mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge branch 'main' into feat/flowy-overlay
This commit is contained in:
commit
9b5184cd72
1
.github/workflows/ci.yaml
vendored
1
.github/workflows/ci.yaml
vendored
@ -59,6 +59,7 @@ jobs:
|
|||||||
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
|
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y dart curl build-essential libsqlite3-dev libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev
|
sudo apt-get install -y dart curl build-essential libsqlite3-dev libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev
|
||||||
|
sudo apt-get install keybinder-3.0
|
||||||
elif [ "$RUNNER_OS" == "macOS" ]; then
|
elif [ "$RUNNER_OS" == "macOS" ]; then
|
||||||
echo 'do nothing'
|
echo 'do nothing'
|
||||||
fi
|
fi
|
||||||
|
4
.github/workflows/flowy_editor_test.yml
vendored
4
.github/workflows/flowy_editor_test.yml
vendored
@ -4,10 +4,14 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- "main"
|
- "main"
|
||||||
|
paths:
|
||||||
|
- "frontend/app_flowy/packages/appflowy_editor"
|
||||||
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- "main"
|
- "main"
|
||||||
|
paths:
|
||||||
|
- "frontend/app_flowy/packages/appflowy_editor"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
|
1
.github/workflows/release.yml
vendored
1
.github/workflows/release.yml
vendored
@ -58,6 +58,7 @@ jobs:
|
|||||||
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
|
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y build-essential libsqlite3-dev libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev
|
sudo apt-get install -y build-essential libsqlite3-dev libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev
|
||||||
|
sudo apt-get install keybinder-3.0
|
||||||
source $HOME/.cargo/env
|
source $HOME/.cargo/env
|
||||||
cargo install --force cargo-make
|
cargo install --force cargo-make
|
||||||
cargo install --force duckscript_cli
|
cargo install --force duckscript_cli
|
||||||
|
37
CHANGELOG.md
37
CHANGELOG.md
@ -1,6 +1,29 @@
|
|||||||
# Release Notes
|
# Release Notes
|
||||||
|
|
||||||
## Version 0.0.4 - 2022-06-06
|
## Version 0.0.5 - beta.2 - beta.1 - 09/01/2022
|
||||||
|
|
||||||
|
New features
|
||||||
|
- Board-view database
|
||||||
|
- Support start editing after creating a new card
|
||||||
|
- Support editing the card directly by clicking the edit button
|
||||||
|
- Add the `No Status` column to display the cards while their status is empty
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
- Optimize insert card animation
|
||||||
|
- Fix some UI bugs
|
||||||
|
|
||||||
|
## Version 0.0.5 - beta.1 - 08/25/2022
|
||||||
|
|
||||||
|
New features
|
||||||
|
- Board-view database
|
||||||
|
- Group by single select
|
||||||
|
- drag and drop cards
|
||||||
|
- insert / delete cards
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
## Version 0.0.4 - 06/06/2022
|
||||||
- Drag to adjust the width of a column
|
- Drag to adjust the width of a column
|
||||||
- Upgrade to Flutter 3.0
|
- Upgrade to Flutter 3.0
|
||||||
- Native support for M1 chip
|
- Native support for M1 chip
|
||||||
@ -12,12 +35,12 @@
|
|||||||
- Fixed some bugs
|
- Fixed some bugs
|
||||||
|
|
||||||
|
|
||||||
## Version 0.0.4 - beta.3 - 2022-05-02
|
## Version 0.0.4 - beta.3 - 05/02/2022
|
||||||
- Drag to reorder app/ view/ field
|
- Drag to reorder app/ view/ field
|
||||||
- Row record open as a page
|
- Row record open as a page
|
||||||
- Auto resize the height of the row in the grid
|
- Auto resize the height of the row in the grid
|
||||||
- Support more number formats
|
- Support more number formats
|
||||||
- Search column options, supporting Single select, Multi-select, and number format
|
- Search column options, supporting Single-select, Multi-select, and number format
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@ -27,7 +50,7 @@
|
|||||||
- Fixed some bugs
|
- Fixed some bugs
|
||||||
|
|
||||||
|
|
||||||
## Version 0.0.4 - beta.2 - 2022-04-11
|
## Version 0.0.4 - beta.2 - 04/11/2022
|
||||||
|
|
||||||
- Support properties: Text, Number, Date, Checkbox, Select, Multi-select
|
- Support properties: Text, Number, Date, Checkbox, Select, Multi-select
|
||||||
- Insert / delete rows
|
- Insert / delete rows
|
||||||
@ -35,16 +58,16 @@
|
|||||||
- Edit property
|
- Edit property
|
||||||

|

|
||||||
|
|
||||||
## Version 0.0.4 - beta.1 - 2022-04-08
|
## Version 0.0.4 - beta.1 - 04/08/2022
|
||||||
v0.0.4 - beta.1 is pre-release
|
v0.0.4 - beta.1 is pre-release
|
||||||
|
|
||||||
New features
|
New features
|
||||||
- Table-view database
|
- Table-view database
|
||||||
- supported column types: Text, Checbox, Single-select, Multi-select, Numbers
|
- supported column types: Text, Checkbox, Single-select, Multi-select, Numbers
|
||||||
- hide / delete columns
|
- hide / delete columns
|
||||||
- insert rows
|
- insert rows
|
||||||
|
|
||||||
## Version 0.0.3 - 2022-02-23
|
## Version 0.0.3 - 02/23/2022
|
||||||
v0.0.3 is production ready, available on Linux, macOS, and Windows
|
v0.0.3 is production ready, available on Linux, macOS, and Windows
|
||||||
|
|
||||||
New features
|
New features
|
||||||
|
38
frontend/.vscode/launch.json
vendored
38
frontend/.vscode/launch.json
vendored
@ -16,6 +16,18 @@
|
|||||||
},
|
},
|
||||||
"cwd": "${workspaceRoot}/app_flowy"
|
"cwd": "${workspaceRoot}/app_flowy"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// This task builds the Rust and Dart code of AppFlowy for android.
|
||||||
|
"name": "AF: Run Android",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "./lib/main.dart",
|
||||||
|
"type": "dart",
|
||||||
|
"preLaunchTask": "AF: build_mobile_sdk",
|
||||||
|
"env": {
|
||||||
|
"RUST_LOG": "info"
|
||||||
|
},
|
||||||
|
"cwd": "${workspaceRoot}/app_flowy"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "AF: Debug Rust",
|
"name": "AF: Debug Rust",
|
||||||
"request": "attach",
|
"request": "attach",
|
||||||
@ -48,6 +60,21 @@
|
|||||||
},
|
},
|
||||||
"cwd": "${workspaceRoot}/app_flowy"
|
"cwd": "${workspaceRoot}/app_flowy"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// 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 (Android)",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "./lib/main.dart",
|
||||||
|
"type": "dart",
|
||||||
|
"preLaunchTask": "AF: Clean + Rebuild All (Android)",
|
||||||
|
"env": {
|
||||||
|
"RUST_LOG": "info"
|
||||||
|
},
|
||||||
|
"cwd": "${workspaceRoot}/app_flowy"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "AF: Build All (rustlog: trace)",
|
"name": "AF: Build All (rustlog: trace)",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
@ -59,6 +86,17 @@
|
|||||||
},
|
},
|
||||||
"cwd": "${workspaceRoot}/app_flowy"
|
"cwd": "${workspaceRoot}/app_flowy"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "AF: Build All Android (rustlog: trace)",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "./lib/main.dart",
|
||||||
|
"type": "dart",
|
||||||
|
"preLaunchTask": "AF: build_mobile_sdk",
|
||||||
|
"env": {
|
||||||
|
"RUST_LOG": "trace"
|
||||||
|
},
|
||||||
|
"cwd": "${workspaceRoot}/app_flowy"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "AF: app_flowy (profile mode)",
|
"name": "AF: app_flowy (profile mode)",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
|
27
frontend/.vscode/tasks.json
vendored
27
frontend/.vscode/tasks.json
vendored
@ -27,6 +27,33 @@
|
|||||||
"panel": "new"
|
"panel": "new"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"label": "AF: Clean + Rebuild All (Android)",
|
||||||
|
"type": "shell",
|
||||||
|
"dependsOrder": "sequence",
|
||||||
|
"dependsOn": [
|
||||||
|
"AF: Rust Clean",
|
||||||
|
"AF: Flutter Clean",
|
||||||
|
"AF: build_flowy_sdk_for_android",
|
||||||
|
"AF: Flutter Pub Get",
|
||||||
|
"AF: Flutter Package Get",
|
||||||
|
"AF: Generate Language Files",
|
||||||
|
"AF: Generate Freezed Files",
|
||||||
|
],
|
||||||
|
"presentation": {
|
||||||
|
"reveal": "always",
|
||||||
|
"panel": "new",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "AF: build_flowy_sdk_for_android",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "cargo make --profile development-android flowy-sdk-dev-android",
|
||||||
|
"group": "build",
|
||||||
|
"options": {
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "AF: build_flowy_sdk",
|
"label": "AF: build_flowy_sdk",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
|
@ -22,7 +22,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
|
|||||||
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
|
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
|
||||||
CARGO_MAKE_CRATE_NAME = "dart-ffi"
|
CARGO_MAKE_CRATE_NAME = "dart-ffi"
|
||||||
LIB_NAME = "dart_ffi"
|
LIB_NAME = "dart_ffi"
|
||||||
CURRENT_APP_VERSION = "0.0.4"
|
CURRENT_APP_VERSION = "0.0.5"
|
||||||
FEATURES = "flutter"
|
FEATURES = "flutter"
|
||||||
PRODUCT_NAME = "AppFlowy"
|
PRODUCT_NAME = "AppFlowy"
|
||||||
# CRATE_TYPE: https://doc.rust-lang.org/reference/linkage.html
|
# CRATE_TYPE: https://doc.rust-lang.org/reference/linkage.html
|
||||||
@ -161,6 +161,11 @@ TARGET_OS = "ios"
|
|||||||
FLUTTER_OUTPUT_DIR = "Release"
|
FLUTTER_OUTPUT_DIR = "Release"
|
||||||
PRODUCT_EXT = "ipa"
|
PRODUCT_EXT = "ipa"
|
||||||
|
|
||||||
|
[env.development-android]
|
||||||
|
BUILD_FLAG = "debug"
|
||||||
|
TARGET_OS = "android"
|
||||||
|
CRATE_TYPE = "cdylib"
|
||||||
|
FLUTTER_OUTPUT_DIR = "Debug"
|
||||||
|
|
||||||
[tasks.setup-crate-type]
|
[tasks.setup-crate-type]
|
||||||
private = true
|
private = true
|
||||||
|
64
frontend/app_flowy/android/README.md
Normal file
64
frontend/app_flowy/android/README.md
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# Description
|
||||||
|
|
||||||
|
This is a guide on how to build the rust SDK for AppFlowy on android.
|
||||||
|
Compiling the sdk is easy it just needs a few tweaks.
|
||||||
|
When compiling for android we need the following pre-requisites:
|
||||||
|
|
||||||
|
- Android NDK Tools. (v24 has been tested).
|
||||||
|
- Cargo NDK. (@latest version).
|
||||||
|
|
||||||
|
**Getting the tools**
|
||||||
|
- Install cargo-ndk ```bash cargo install cargo-ndk```.
|
||||||
|
- [Download](https://developer.android.com/ndk/downloads/) Android NDK version 24.
|
||||||
|
- When downloading Android NDK you can get the compressed version as a standalone from the site.
|
||||||
|
Or you can download it through [Android Studio](https://developer.android.com/studio).
|
||||||
|
- After downloading the two you need to set the environment variables. For Windows that's a seperate process.
|
||||||
|
On MacOs and Linux the process is similar.
|
||||||
|
- The variables needed are '$ANDROID_NDK_HOME', this will point to where the NDK is located.
|
||||||
|
---
|
||||||
|
|
||||||
|
**Cargo Config File**
|
||||||
|
This code needs to be written in ~/.cargo/config, this helps cargo know where to locate the android tools(linker and archiver).
|
||||||
|
**NB** Keep in mind just replace 'user' with your own user name. Or just point it to the location of where you put the NDK.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[target.aarch64-linux-android]
|
||||||
|
ar = "/home/user/Android/Sdk/ndk/24.0.8215888/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar"
|
||||||
|
linker = "/home/user/Android/Sdk/ndk/24.0.8215888/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android29-clang"
|
||||||
|
|
||||||
|
[target.armv7-linux-androideabi]
|
||||||
|
ar = "/home/user/Android/Sdk/ndk/24.0.8215888/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar"
|
||||||
|
linker = "/home/user/Android/Sdk/ndk/24.0.8215888/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi29-clang"
|
||||||
|
|
||||||
|
[target.i686-linux-android]
|
||||||
|
ar = "/home/user/Android/Sdk/ndk/24.0.8215888/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar"
|
||||||
|
linker = "/home/user/Android/Sdk/ndk/24.0.8215888/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android29-clang"
|
||||||
|
|
||||||
|
[target.x86_64-linux-android]
|
||||||
|
ar = "/home/user/Android/Sdk/ndk/24.0.8215888/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar"
|
||||||
|
linker = "/home/user/Android/Sdk/ndk/24.0.8215888/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android29-clang"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Clang Fix**
|
||||||
|
In order to get clang to work properly with version 24 you need to create this file.
|
||||||
|
libgcc.a, then add this one line.
|
||||||
|
```
|
||||||
|
INPUT(-lunwind)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Folder path: 'Android/Sdk/ndk/24.0.8215888/toolchains/llvm/prebuilt/linux-x86_64/lib64/clang/14.0.1/lib/linux'.**
|
||||||
|
After that you have to copy this file into three different folders namely aarch64, arm, i386 and x86_64.
|
||||||
|
We have to do this so we Android NDK can find clang on our system, if we used NDK 22 we wouldnt have to do this process.
|
||||||
|
Though using NDK v22 will not give us alot of features to work with.
|
||||||
|
This github [issue](https://github.com/fzyzcjy/flutter_rust_bridge/issues/419) explains the reason why we are doing this.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Android NDK**
|
||||||
|
|
||||||
|
After installing the NDK tools for android you should export the PATH to your config file
|
||||||
|
(.vimrc, .zshrc, .profile, .bashrc file), That way it can be found.
|
||||||
|
|
||||||
|
```vim
|
||||||
|
export PATH=/home/sean/Android/Sdk/ndk/24.0.8215888
|
||||||
|
```
|
@ -26,7 +26,8 @@ apply plugin: 'kotlin-android'
|
|||||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 30
|
compileSdkVersion 31
|
||||||
|
ndkVersion "24.0.8215888"
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
@ -39,21 +40,26 @@ android {
|
|||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main.java.srcDirs += 'src/main/kotlin'
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
|
main.jniLibs.srcDirs += 'jniLibs/'
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
applicationId "com.example.app_flowy"
|
applicationId "com.example.app_flowy"
|
||||||
minSdkVersion 16
|
minSdkVersion 19
|
||||||
targetSdkVersion 30
|
targetSdkVersion 31
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
|
multiDexEnabled true
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
// TODO: Add your own signing config for the release build.
|
// TODO: Add your own signing config for the release build.
|
||||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||||
|
minifyEnabled true
|
||||||
|
shrinkResources true
|
||||||
|
|
||||||
signingConfig signingConfigs.debug
|
signingConfig signingConfigs.debug
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -65,4 +71,5 @@ flutter {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
implementation "com.android.support:multidex:2.0.1"
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
package="com.example.app_flowy">
|
package="com.example.app_flowy">
|
||||||
<application
|
<application
|
||||||
android:label="app_flowy"
|
android:label="app_flowy"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:name="${applicationName}">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.3.50'
|
ext.kotlin_version = '1.6.10'
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
org.gradle.jvmargs=-Xmx1536M
|
org.gradle.jvmargs=-Xmx1536M
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
|
org.gradle.caching=true
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
#Fri Jun 23 08:50:38 CEST 2017
|
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-all.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
|
|
||||||
|
@ -9,3 +9,19 @@ localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
|
|||||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||||
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
|
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
|
||||||
|
|
||||||
|
|
||||||
|
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
|
||||||
|
|
||||||
|
def plugins = new Properties()
|
||||||
|
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
|
||||||
|
|
||||||
|
if(pluginsFile.exists()){
|
||||||
|
pluginsFile.withReader('UTF-8'){reader -> plugins.load(reader)}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins.each{name, path ->
|
||||||
|
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
|
||||||
|
include ":$name"
|
||||||
|
project(":$name").projectDir = pluginDirectory
|
||||||
|
}
|
@ -141,5 +141,9 @@
|
|||||||
"lightLabel": "Mode Clar",
|
"lightLabel": "Mode Clar",
|
||||||
"darkLabel": "Mode Fosc"
|
"darkLabel": "Mode Fosc"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"sideBar": {
|
||||||
|
"openSidebar": "Open sidebar",
|
||||||
|
"closeSidebar": "Close sidebar"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,6 +141,10 @@
|
|||||||
"lightLabel": "Heller Modus",
|
"lightLabel": "Heller Modus",
|
||||||
"darkLabel": "Dunkler Modus"
|
"darkLabel": "Dunkler Modus"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"sideBar": {
|
||||||
|
"openSidebar": "Open sidebar",
|
||||||
|
"closeSidebar": "Close sidebar"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -95,7 +95,13 @@
|
|||||||
"tooltip": {
|
"tooltip": {
|
||||||
"lightMode": "Switch to Light mode",
|
"lightMode": "Switch to Light mode",
|
||||||
"darkMode": "Switch to Dark mode",
|
"darkMode": "Switch to Dark mode",
|
||||||
"openAsPage": "Open as a Page"
|
"openAsPage": "Open as a Page",
|
||||||
|
"addNewRow": "Add a new row",
|
||||||
|
"openMenu": "Click to open menu"
|
||||||
|
},
|
||||||
|
"sideBar": {
|
||||||
|
"closeSidebar": "Close side bar",
|
||||||
|
"openSidebar": "Open side bar"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"export": {
|
"export": {
|
||||||
@ -183,7 +189,8 @@
|
|||||||
"addSelectOption": "Add an option",
|
"addSelectOption": "Add an option",
|
||||||
"optionTitle": "Options",
|
"optionTitle": "Options",
|
||||||
"addOption": "Add option",
|
"addOption": "Add option",
|
||||||
"editProperty": "Edit property"
|
"editProperty": "Edit property",
|
||||||
|
"newColumn": "New column"
|
||||||
},
|
},
|
||||||
"row": {
|
"row": {
|
||||||
"duplicate": "Duplicate",
|
"duplicate": "Duplicate",
|
||||||
@ -215,5 +222,10 @@
|
|||||||
"timeHintTextInTwelveHour": "12:00 AM",
|
"timeHintTextInTwelveHour": "12:00 AM",
|
||||||
"timeHintTextInTwentyFourHour": "12:00"
|
"timeHintTextInTwentyFourHour": "12:00"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"board": {
|
||||||
|
"column": {
|
||||||
|
"create_new_card": "New"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -213,5 +213,9 @@
|
|||||||
"timeHintTextInTwelveHour": "12:00 AM",
|
"timeHintTextInTwelveHour": "12:00 AM",
|
||||||
"timeHintTextInTwentyFourHour": "12:00"
|
"timeHintTextInTwentyFourHour": "12:00"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"sideBar": {
|
||||||
|
"openSidebar": "Open sidebar",
|
||||||
|
"closeSidebar": "Close sidebar"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,5 +141,9 @@
|
|||||||
"lightLabel": "Mode clair",
|
"lightLabel": "Mode clair",
|
||||||
"darkLabel": "Mode sombre"
|
"darkLabel": "Mode sombre"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"sideBar": {
|
||||||
|
"openSidebar": "Open sidebar",
|
||||||
|
"closeSidebar": "Close sidebar"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -142,6 +142,10 @@
|
|||||||
"darkLabel": "Mode sombre"
|
"darkLabel": "Mode sombre"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"sideBar": {
|
||||||
|
"openSidebar": "Open sidebar",
|
||||||
|
"closeSidebar": "Close sidebar"
|
||||||
|
},
|
||||||
"grid": {
|
"grid": {
|
||||||
"settings": {
|
"settings": {
|
||||||
"filter": "Filtrer",
|
"filter": "Filtrer",
|
||||||
|
@ -141,5 +141,9 @@
|
|||||||
"lightLabel": "Világos mód",
|
"lightLabel": "Világos mód",
|
||||||
"darkLabel": "Éjjeli mód"
|
"darkLabel": "Éjjeli mód"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"sideBar": {
|
||||||
|
"openSidebar": "Open sidebar",
|
||||||
|
"closeSidebar": "Close sidebar"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -214,5 +214,9 @@
|
|||||||
"timeHintTextInTwelveHour": "12:00 AM",
|
"timeHintTextInTwelveHour": "12:00 AM",
|
||||||
"timeHintTextInTwentyFourHour": "12:00"
|
"timeHintTextInTwentyFourHour": "12:00"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"sideBar": {
|
||||||
|
"openSidebar": "Open sidebar",
|
||||||
|
"closeSidebar": "Close sidebar"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -147,5 +147,9 @@
|
|||||||
},
|
},
|
||||||
"document":{
|
"document":{
|
||||||
"menuName":"Documento"
|
"menuName":"Documento"
|
||||||
|
},
|
||||||
|
"sideBar": {
|
||||||
|
"openSidebar": "Open sidebar",
|
||||||
|
"closeSidebar": "Close sidebar"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,5 +195,9 @@
|
|||||||
"pannelTitle": "選択候補を検索 または 作成する",
|
"pannelTitle": "選択候補を検索 または 作成する",
|
||||||
"searchOption": "選択候補を検索"
|
"searchOption": "選択候補を検索"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"sideBar": {
|
||||||
|
"openSidebar": "Open sidebar",
|
||||||
|
"closeSidebar": "Close sidebar"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -141,5 +141,9 @@
|
|||||||
"lightLabel": "Tryb Jasny",
|
"lightLabel": "Tryb Jasny",
|
||||||
"darkLabel": "Tryb Ciemny"
|
"darkLabel": "Tryb Ciemny"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"sideBar": {
|
||||||
|
"openSidebar": "Open sidebar",
|
||||||
|
"closeSidebar": "Close sidebar"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,6 +141,10 @@
|
|||||||
"lightLabel": "Modo Claro",
|
"lightLabel": "Modo Claro",
|
||||||
"darkLabel": "Modo Escuro"
|
"darkLabel": "Modo Escuro"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"sideBar": {
|
||||||
|
"openSidebar": "Open sidebar",
|
||||||
|
"closeSidebar": "Close sidebar"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,6 +141,10 @@
|
|||||||
"lightLabel": "Modo Claro",
|
"lightLabel": "Modo Claro",
|
||||||
"darkLabel": "Modo Escuro"
|
"darkLabel": "Modo Escuro"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"sideBar": {
|
||||||
|
"openSidebar": "Open sidebar",
|
||||||
|
"closeSidebar": "Close sidebar"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,6 +203,10 @@
|
|||||||
"timeHintTextInTwelveHour": "12:00 AM",
|
"timeHintTextInTwelveHour": "12:00 AM",
|
||||||
"timeHintTextInTwentyFourHour": "12:00"
|
"timeHintTextInTwentyFourHour": "12:00"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"sideBar": {
|
||||||
|
"openSidebar": "Open sidebar",
|
||||||
|
"closeSidebar": "Close sidebar"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -141,5 +141,9 @@
|
|||||||
"lightLabel": "Aydınlık Mod",
|
"lightLabel": "Aydınlık Mod",
|
||||||
"darkLabel": "Karanlık Mod"
|
"darkLabel": "Karanlık Mod"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"sideBar": {
|
||||||
|
"openSidebar": "Open sidebar",
|
||||||
|
"closeSidebar": "Close sidebar"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,8 +93,14 @@
|
|||||||
"highlight": "高亮"
|
"highlight": "高亮"
|
||||||
},
|
},
|
||||||
"tooltip": {
|
"tooltip": {
|
||||||
"lightMode": "切换到灯光模式",
|
"lightMode": "切换到亮色模式",
|
||||||
"darkMode": "切换到暗模式"
|
"darkMode": "切换到暗色模式"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"export": {
|
||||||
|
"markdown": "导出笔记为Markdown文档",
|
||||||
|
"path": "Documents/flowy"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"contactsPage": {
|
"contactsPage": {
|
||||||
"title": "联系人",
|
"title": "联系人",
|
||||||
@ -135,11 +141,82 @@
|
|||||||
"menu": {
|
"menu": {
|
||||||
"appearance": "外观",
|
"appearance": "外观",
|
||||||
"language": "语言",
|
"language": "语言",
|
||||||
|
"user": "用户",
|
||||||
"open": "打开设置"
|
"open": "打开设置"
|
||||||
},
|
},
|
||||||
"appearance": {
|
"appearance": {
|
||||||
"lightLabel": "日间模式",
|
"lightLabel": "日间模式",
|
||||||
"darkLabel": "夜间模式"
|
"darkLabel": "夜间模式"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"sideBar": {
|
||||||
|
"openSidebar": "打开侧边栏",
|
||||||
|
"closeSidebar": "关闭侧边栏"
|
||||||
|
},
|
||||||
|
"grid": {
|
||||||
|
"settings": {
|
||||||
|
"filter": "过滤器",
|
||||||
|
"sortBy": "排序",
|
||||||
|
"Properties": "属性"
|
||||||
|
},
|
||||||
|
"field": {
|
||||||
|
"hide": "隐藏",
|
||||||
|
"insertLeft": "左侧插入",
|
||||||
|
"insertRight": "右侧插入",
|
||||||
|
"duplicate": "拷贝",
|
||||||
|
"delete": "删除",
|
||||||
|
"textFieldName": "文本",
|
||||||
|
"checkboxFieldName": "勾选框",
|
||||||
|
"dateFieldName": "日期",
|
||||||
|
"numberFieldName": "数字",
|
||||||
|
"singleSelectFieldName": "单项选择器",
|
||||||
|
"multiSelectFieldName": "多项选择器",
|
||||||
|
"urlFieldName": "链接",
|
||||||
|
"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": "搜索标签"
|
||||||
|
},
|
||||||
|
"menuName": "网格"
|
||||||
|
},
|
||||||
|
"document": {
|
||||||
|
"menuName": "文档",
|
||||||
|
"date": {
|
||||||
|
"timeHintTextInTwelveHour": "12:00 AM",
|
||||||
|
"timeHintTextInTwentyFourHour": "12:00"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -214,5 +214,9 @@
|
|||||||
"timeHintTextInTwelveHour": "12:00 AM",
|
"timeHintTextInTwelveHour": "12:00 AM",
|
||||||
"timeHintTextInTwentyFourHour": "12:00"
|
"timeHintTextInTwentyFourHour": "12:00"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"sideBar": {
|
||||||
|
"openSidebar": "Open sidebar",
|
||||||
|
"closeSidebar": "Close sidebar"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -31,10 +31,10 @@ class MoveWindowDetector extends StatefulWidget {
|
|||||||
final Widget? child;
|
final Widget? child;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_MoveWindowDetectorState createState() => _MoveWindowDetectorState();
|
MoveWindowDetectorState createState() => MoveWindowDetectorState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MoveWindowDetectorState extends State<MoveWindowDetector> {
|
class MoveWindowDetectorState extends State<MoveWindowDetector> {
|
||||||
double winX = 0;
|
double winX = 0;
|
||||||
double winY = 0;
|
double winY = 0;
|
||||||
|
|
||||||
@ -59,7 +59,8 @@ class _MoveWindowDetectorState extends State<MoveWindowDetector> {
|
|||||||
final double dy = windowPos[1];
|
final double dy = windowPos[1];
|
||||||
final deltaX = details.globalPosition.dx - winX;
|
final deltaX = details.globalPosition.dx - winX;
|
||||||
final deltaY = details.globalPosition.dy - winY;
|
final deltaY = details.globalPosition.dy - winY;
|
||||||
await CocoaWindowChannel.instance.setWindowPosition(Offset(dx + deltaX, dy - deltaY));
|
await CocoaWindowChannel.instance
|
||||||
|
.setWindowPosition(Offset(dx + deltaX, dy - deltaY));
|
||||||
},
|
},
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
);
|
);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:app_flowy/startup/startup.dart';
|
import 'package:app_flowy/startup/startup.dart';
|
||||||
import 'package:app_flowy/user/presentation/splash_screen.dart';
|
import 'package:app_flowy/user/presentation/splash_screen.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:hotkey_manager/hotkey_manager.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class FlowyApp implements EntryPoint {
|
class FlowyApp implements EntryPoint {
|
||||||
@ -14,5 +15,8 @@ void main() async {
|
|||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await EasyLocalization.ensureInitialized();
|
await EasyLocalization.ensureInitialized();
|
||||||
|
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
await hotKeyManager.unregisterAll();
|
||||||
|
|
||||||
await FlowyRunner.run(FlowyApp());
|
await FlowyRunner.run(FlowyApp());
|
||||||
}
|
}
|
||||||
|
@ -20,19 +20,19 @@ import 'group_controller.dart';
|
|||||||
part 'board_bloc.freezed.dart';
|
part 'board_bloc.freezed.dart';
|
||||||
|
|
||||||
class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||||
final BoardDataController _dataController;
|
final BoardDataController _gridDataController;
|
||||||
late final AFBoardDataController afBoardDataController;
|
late final AFBoardDataController boardController;
|
||||||
final MoveRowFFIService _rowService;
|
final MoveRowFFIService _rowService;
|
||||||
LinkedHashMap<String, GroupController> groupControllers = LinkedHashMap.new();
|
LinkedHashMap<String, GroupController> groupControllers = LinkedHashMap();
|
||||||
|
|
||||||
GridFieldCache get fieldCache => _dataController.fieldCache;
|
GridFieldCache get fieldCache => _gridDataController.fieldCache;
|
||||||
String get gridId => _dataController.gridId;
|
String get gridId => _gridDataController.gridId;
|
||||||
|
|
||||||
BoardBloc({required ViewPB view})
|
BoardBloc({required ViewPB view})
|
||||||
: _rowService = MoveRowFFIService(gridId: view.id),
|
: _rowService = MoveRowFFIService(gridId: view.id),
|
||||||
_dataController = BoardDataController(view: view),
|
_gridDataController = BoardDataController(view: view),
|
||||||
super(BoardState.initial(view.id)) {
|
super(BoardState.initial(view.id)) {
|
||||||
afBoardDataController = AFBoardDataController(
|
boardController = AFBoardDataController(
|
||||||
onMoveColumn: (
|
onMoveColumn: (
|
||||||
fromColumnId,
|
fromColumnId,
|
||||||
fromIndex,
|
fromIndex,
|
||||||
@ -69,31 +69,51 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
|||||||
_startListening();
|
_startListening();
|
||||||
await _loadGrid(emit);
|
await _loadGrid(emit);
|
||||||
},
|
},
|
||||||
createRow: (groupId) async {
|
createBottomRow: (groupId) async {
|
||||||
final result = await _dataController.createBoardCard(groupId);
|
final startRowId = groupControllers[groupId]?.lastRow()?.id;
|
||||||
|
final result = await _gridDataController.createBoardCard(
|
||||||
|
groupId,
|
||||||
|
startRowId: startRowId,
|
||||||
|
);
|
||||||
result.fold(
|
result.fold(
|
||||||
(rowPB) {
|
(_) {},
|
||||||
emit(state.copyWith(editingRow: some(rowPB)));
|
|
||||||
},
|
|
||||||
(err) => Log.error(err),
|
(err) => Log.error(err),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
createHeaderRow: (String groupId) async {
|
||||||
|
final result = await _gridDataController.createBoardCard(groupId);
|
||||||
|
result.fold(
|
||||||
|
(_) {},
|
||||||
|
(err) => Log.error(err),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
didCreateRow: (String groupId, RowPB row, int? index) {
|
||||||
|
emit(state.copyWith(
|
||||||
|
editingRow: Some(BoardEditingRow(
|
||||||
|
columnId: groupId,
|
||||||
|
row: row,
|
||||||
|
index: index,
|
||||||
|
)),
|
||||||
|
));
|
||||||
|
},
|
||||||
endEditRow: (rowId) {
|
endEditRow: (rowId) {
|
||||||
assert(state.editingRow.isSome());
|
assert(state.editingRow.isSome());
|
||||||
state.editingRow.fold(() => null, (row) {
|
state.editingRow.fold(() => null, (editingRow) {
|
||||||
assert(row.id == rowId);
|
assert(editingRow.row.id == rowId);
|
||||||
emit(state.copyWith(editingRow: none()));
|
emit(state.copyWith(editingRow: none()));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
didReceiveGridUpdate: (GridPB grid) {
|
didReceiveGridUpdate: (GridPB grid) {
|
||||||
emit(state.copyWith(grid: Some(grid)));
|
emit(state.copyWith(grid: Some(grid)));
|
||||||
},
|
},
|
||||||
didReceiveRows: (List<RowInfo> rowInfos) {
|
|
||||||
emit(state.copyWith(rowInfos: rowInfos));
|
|
||||||
},
|
|
||||||
didReceiveError: (FlowyError error) {
|
didReceiveError: (FlowyError error) {
|
||||||
emit(state.copyWith(noneOrError: some(error)));
|
emit(state.copyWith(noneOrError: some(error)));
|
||||||
},
|
},
|
||||||
|
didReceiveGroups: (List<GroupPB> groups) {
|
||||||
|
emit(state.copyWith(
|
||||||
|
groupIds: groups.map((group) => group.groupId).toList(),
|
||||||
|
));
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -126,7 +146,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
await _dataController.dispose();
|
await _gridDataController.dispose();
|
||||||
for (final controller in groupControllers.values) {
|
for (final controller in groupControllers.values) {
|
||||||
controller.dispose();
|
controller.dispose();
|
||||||
}
|
}
|
||||||
@ -135,7 +155,12 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
|||||||
|
|
||||||
void initializeGroups(List<GroupPB> groups) {
|
void initializeGroups(List<GroupPB> groups) {
|
||||||
for (final group in groups) {
|
for (final group in groups) {
|
||||||
final delegate = GroupControllerDelegateImpl(afBoardDataController);
|
final delegate = GroupControllerDelegateImpl(
|
||||||
|
controller: boardController,
|
||||||
|
onNewColumnItem: (groupId, row, index) {
|
||||||
|
add(BoardEvent.didCreateRow(groupId, row, index));
|
||||||
|
},
|
||||||
|
);
|
||||||
final controller = GroupController(
|
final controller = GroupController(
|
||||||
gridId: state.gridId,
|
gridId: state.gridId,
|
||||||
group: group,
|
group: group,
|
||||||
@ -147,12 +172,12 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
GridRowCache? getRowCache(String blockId) {
|
GridRowCache? getRowCache(String blockId) {
|
||||||
final GridBlockCache? blockCache = _dataController.blocks[blockId];
|
final GridBlockCache? blockCache = _gridDataController.blocks[blockId];
|
||||||
return blockCache?.rowCache;
|
return blockCache?.rowCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _startListening() {
|
void _startListening() {
|
||||||
_dataController.addListener(
|
_gridDataController.addListener(
|
||||||
onGridChanged: (grid) {
|
onGridChanged: (grid) {
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
add(BoardEvent.didReceiveGridUpdate(grid));
|
add(BoardEvent.didReceiveGridUpdate(grid));
|
||||||
@ -162,17 +187,31 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
|||||||
List<AFBoardColumnData> columns = groups.map((group) {
|
List<AFBoardColumnData> columns = groups.map((group) {
|
||||||
return AFBoardColumnData(
|
return AFBoardColumnData(
|
||||||
id: group.groupId,
|
id: group.groupId,
|
||||||
desc: group.desc,
|
name: group.desc,
|
||||||
items: _buildRows(group.rows),
|
items: _buildRows(group),
|
||||||
customData: group,
|
customData: group,
|
||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
afBoardDataController.addColumns(columns);
|
boardController.addColumns(columns);
|
||||||
initializeGroups(groups);
|
initializeGroups(groups);
|
||||||
|
add(BoardEvent.didReceiveGroups(groups));
|
||||||
},
|
},
|
||||||
onRowsChanged: (List<RowInfo> rowInfos, RowsChangedReason reason) {
|
onDeletedGroup: (groupIds) {
|
||||||
add(BoardEvent.didReceiveRows(rowInfos));
|
//
|
||||||
|
},
|
||||||
|
onInsertedGroup: (insertedGroups) {
|
||||||
|
//
|
||||||
|
},
|
||||||
|
onUpdatedGroup: (updatedGroups) {
|
||||||
|
//
|
||||||
|
for (final group in updatedGroups) {
|
||||||
|
final columnController =
|
||||||
|
boardController.getColumnController(group.groupId);
|
||||||
|
if (columnController != null) {
|
||||||
|
columnController.updateColumnName(group.desc);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onError: (err) {
|
onError: (err) {
|
||||||
Log.error(err);
|
Log.error(err);
|
||||||
@ -180,16 +219,19 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<AFColumnItem> _buildRows(List<RowPB> rows) {
|
List<AFColumnItem> _buildRows(GroupPB group) {
|
||||||
final items = rows.map((row) {
|
final items = group.rows.map((row) {
|
||||||
return BoardColumnItem(row: row);
|
return BoardColumnItem(
|
||||||
|
row: row,
|
||||||
|
fieldId: group.fieldId,
|
||||||
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
return <AFColumnItem>[...items];
|
return <AFColumnItem>[...items];
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadGrid(Emitter<BoardState> emit) async {
|
Future<void> _loadGrid(Emitter<BoardState> emit) async {
|
||||||
final result = await _dataController.loadData();
|
final result = await _gridDataController.loadData();
|
||||||
result.fold(
|
result.fold(
|
||||||
(grid) => emit(
|
(grid) => emit(
|
||||||
state.copyWith(loadingState: GridLoadingState.finish(left(unit))),
|
state.copyWith(loadingState: GridLoadingState.finish(left(unit))),
|
||||||
@ -203,15 +245,21 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
|||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class BoardEvent with _$BoardEvent {
|
class BoardEvent with _$BoardEvent {
|
||||||
const factory BoardEvent.initial() = InitialGrid;
|
const factory BoardEvent.initial() = _InitialBoard;
|
||||||
const factory BoardEvent.createRow(String groupId) = _CreateRow;
|
const factory BoardEvent.createBottomRow(String groupId) = _CreateBottomRow;
|
||||||
|
const factory BoardEvent.createHeaderRow(String groupId) = _CreateHeaderRow;
|
||||||
|
const factory BoardEvent.didCreateRow(
|
||||||
|
String groupId,
|
||||||
|
RowPB row,
|
||||||
|
int? index,
|
||||||
|
) = _DidCreateRow;
|
||||||
const factory BoardEvent.endEditRow(String rowId) = _EndEditRow;
|
const factory BoardEvent.endEditRow(String rowId) = _EndEditRow;
|
||||||
const factory BoardEvent.didReceiveError(FlowyError error) = _DidReceiveError;
|
const factory BoardEvent.didReceiveError(FlowyError error) = _DidReceiveError;
|
||||||
const factory BoardEvent.didReceiveRows(List<RowInfo> rowInfos) =
|
|
||||||
_DidReceiveRows;
|
|
||||||
const factory BoardEvent.didReceiveGridUpdate(
|
const factory BoardEvent.didReceiveGridUpdate(
|
||||||
GridPB grid,
|
GridPB grid,
|
||||||
) = _DidReceiveGridUpdate;
|
) = _DidReceiveGridUpdate;
|
||||||
|
const factory BoardEvent.didReceiveGroups(List<GroupPB> groups) =
|
||||||
|
_DidReceiveGroups;
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
@ -219,16 +267,16 @@ class BoardState with _$BoardState {
|
|||||||
const factory BoardState({
|
const factory BoardState({
|
||||||
required String gridId,
|
required String gridId,
|
||||||
required Option<GridPB> grid,
|
required Option<GridPB> grid,
|
||||||
required Option<RowPB> editingRow,
|
required List<String> groupIds,
|
||||||
required List<RowInfo> rowInfos,
|
required Option<BoardEditingRow> editingRow,
|
||||||
required GridLoadingState loadingState,
|
required GridLoadingState loadingState,
|
||||||
required Option<FlowyError> noneOrError,
|
required Option<FlowyError> noneOrError,
|
||||||
}) = _BoardState;
|
}) = _BoardState;
|
||||||
|
|
||||||
factory BoardState.initial(String gridId) => BoardState(
|
factory BoardState.initial(String gridId) => BoardState(
|
||||||
rowInfos: [],
|
|
||||||
grid: none(),
|
grid: none(),
|
||||||
gridId: gridId,
|
gridId: gridId,
|
||||||
|
groupIds: [],
|
||||||
editingRow: none(),
|
editingRow: none(),
|
||||||
noneOrError: none(),
|
noneOrError: none(),
|
||||||
loadingState: const _Loading(),
|
loadingState: const _Loading(),
|
||||||
@ -268,39 +316,84 @@ class GridFieldEquatable extends Equatable {
|
|||||||
class BoardColumnItem extends AFColumnItem {
|
class BoardColumnItem extends AFColumnItem {
|
||||||
final RowPB row;
|
final RowPB row;
|
||||||
|
|
||||||
BoardColumnItem({required this.row});
|
final String fieldId;
|
||||||
|
|
||||||
|
final bool requestFocus;
|
||||||
|
|
||||||
|
BoardColumnItem({
|
||||||
|
required this.row,
|
||||||
|
required this.fieldId,
|
||||||
|
this.requestFocus = false,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get id => row.id;
|
String get id => row.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
class CreateCardItem extends AFColumnItem {
|
|
||||||
@override
|
|
||||||
String get id => '$CreateCardItem';
|
|
||||||
}
|
|
||||||
|
|
||||||
class GroupControllerDelegateImpl extends GroupControllerDelegate {
|
class GroupControllerDelegateImpl extends GroupControllerDelegate {
|
||||||
final AFBoardDataController controller;
|
final AFBoardDataController controller;
|
||||||
|
final void Function(String, RowPB, int?) onNewColumnItem;
|
||||||
|
|
||||||
GroupControllerDelegateImpl(this.controller);
|
GroupControllerDelegateImpl({
|
||||||
|
required this.controller,
|
||||||
|
required this.onNewColumnItem,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void insertRow(String groupId, RowPB row, int? index) {
|
void insertRow(GroupPB group, RowPB row, int? index) {
|
||||||
final item = BoardColumnItem(row: row);
|
|
||||||
if (index != null) {
|
if (index != null) {
|
||||||
controller.insertColumnItem(groupId, index, item);
|
final item = BoardColumnItem(row: row, fieldId: group.fieldId);
|
||||||
|
controller.insertColumnItem(group.groupId, index, item);
|
||||||
} else {
|
} else {
|
||||||
controller.addColumnItem(groupId, item);
|
final item = BoardColumnItem(
|
||||||
|
row: row,
|
||||||
|
fieldId: group.fieldId,
|
||||||
|
);
|
||||||
|
controller.addColumnItem(group.groupId, item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void removeRow(String groupId, String rowId) {
|
void removeRow(GroupPB group, String rowId) {
|
||||||
controller.removeColumnItem(groupId, rowId);
|
controller.removeColumnItem(group.groupId, rowId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void updateRow(String groupId, RowPB row) {
|
void updateRow(GroupPB group, RowPB row) {
|
||||||
//
|
controller.updateColumnItem(
|
||||||
|
group.groupId,
|
||||||
|
BoardColumnItem(
|
||||||
|
row: row,
|
||||||
|
fieldId: group.fieldId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void addNewRow(GroupPB group, RowPB row, int? index) {
|
||||||
|
final item = BoardColumnItem(
|
||||||
|
row: row,
|
||||||
|
fieldId: group.fieldId,
|
||||||
|
requestFocus: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (index != null) {
|
||||||
|
controller.insertColumnItem(group.groupId, index, item);
|
||||||
|
} else {
|
||||||
|
controller.addColumnItem(group.groupId, item);
|
||||||
|
}
|
||||||
|
onNewColumnItem(group.groupId, row, index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class BoardEditingRow {
|
||||||
|
String columnId;
|
||||||
|
RowPB row;
|
||||||
|
int? index;
|
||||||
|
|
||||||
|
BoardEditingRow({
|
||||||
|
required this.columnId,
|
||||||
|
required this.row,
|
||||||
|
required this.index,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -10,9 +10,15 @@ import 'dart:async';
|
|||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
|
import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
|
||||||
|
|
||||||
|
import 'board_listener.dart';
|
||||||
|
|
||||||
typedef OnFieldsChanged = void Function(UnmodifiableListView<FieldPB>);
|
typedef OnFieldsChanged = void Function(UnmodifiableListView<FieldPB>);
|
||||||
typedef OnGridChanged = void Function(GridPB);
|
typedef OnGridChanged = void Function(GridPB);
|
||||||
typedef DidLoadGroups = void Function(List<GroupPB>);
|
typedef DidLoadGroups = void Function(List<GroupPB>);
|
||||||
|
typedef OnUpdatedGroup = void Function(List<GroupPB>);
|
||||||
|
typedef OnDeletedGroup = void Function(List<String>);
|
||||||
|
typedef OnInsertedGroup = void Function(List<InsertedGroupPB>);
|
||||||
|
|
||||||
typedef OnRowsChanged = void Function(
|
typedef OnRowsChanged = void Function(
|
||||||
List<RowInfo>,
|
List<RowInfo>,
|
||||||
RowsChangedReason,
|
RowsChangedReason,
|
||||||
@ -23,6 +29,7 @@ class BoardDataController {
|
|||||||
final String gridId;
|
final String gridId;
|
||||||
final GridFFIService _gridFFIService;
|
final GridFFIService _gridFFIService;
|
||||||
final GridFieldCache fieldCache;
|
final GridFieldCache fieldCache;
|
||||||
|
final BoardListener _listener;
|
||||||
|
|
||||||
// key: the block id
|
// key: the block id
|
||||||
final LinkedHashMap<String, GridBlockCache> _blocks;
|
final LinkedHashMap<String, GridBlockCache> _blocks;
|
||||||
@ -44,16 +51,21 @@ class BoardDataController {
|
|||||||
|
|
||||||
BoardDataController({required ViewPB view})
|
BoardDataController({required ViewPB view})
|
||||||
: gridId = view.id,
|
: gridId = view.id,
|
||||||
_blocks = LinkedHashMap.new(),
|
_listener = BoardListener(view.id),
|
||||||
|
// ignore: prefer_collection_literals
|
||||||
|
_blocks = LinkedHashMap(),
|
||||||
_gridFFIService = GridFFIService(gridId: view.id),
|
_gridFFIService = GridFFIService(gridId: view.id),
|
||||||
fieldCache = GridFieldCache(gridId: view.id);
|
fieldCache = GridFieldCache(gridId: view.id);
|
||||||
|
|
||||||
void addListener({
|
void addListener({
|
||||||
OnGridChanged? onGridChanged,
|
required OnGridChanged onGridChanged,
|
||||||
OnFieldsChanged? onFieldsChanged,
|
OnFieldsChanged? onFieldsChanged,
|
||||||
DidLoadGroups? didLoadGroups,
|
required DidLoadGroups didLoadGroups,
|
||||||
OnRowsChanged? onRowsChanged,
|
OnRowsChanged? onRowsChanged,
|
||||||
OnError? onError,
|
required OnUpdatedGroup onUpdatedGroup,
|
||||||
|
required OnDeletedGroup onDeletedGroup,
|
||||||
|
required OnInsertedGroup onInsertedGroup,
|
||||||
|
required OnError? onError,
|
||||||
}) {
|
}) {
|
||||||
_onGridChanged = onGridChanged;
|
_onGridChanged = onGridChanged;
|
||||||
_onFieldsChanged = onFieldsChanged;
|
_onFieldsChanged = onFieldsChanged;
|
||||||
@ -64,6 +76,25 @@ class BoardDataController {
|
|||||||
fieldCache.addListener(onFields: (fields) {
|
fieldCache.addListener(onFields: (fields) {
|
||||||
_onFieldsChanged?.call(UnmodifiableListView(fields));
|
_onFieldsChanged?.call(UnmodifiableListView(fields));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_listener.start(onBoardChanged: (result) {
|
||||||
|
result.fold(
|
||||||
|
(changeset) {
|
||||||
|
if (changeset.updateGroups.isNotEmpty) {
|
||||||
|
onUpdatedGroup.call(changeset.updateGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changeset.insertedGroups.isNotEmpty) {
|
||||||
|
onInsertedGroup.call(changeset.insertedGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changeset.deletedGroups.isNotEmpty) {
|
||||||
|
onDeletedGroup.call(changeset.deletedGroups);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(e) => _onError?.call(e),
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Either<Unit, FlowyError>> loadData() async {
|
Future<Either<Unit, FlowyError>> loadData() async {
|
||||||
@ -88,8 +119,9 @@ class BoardDataController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Either<RowPB, FlowyError>> createBoardCard(String groupId) {
|
Future<Either<RowPB, FlowyError>> createBoardCard(String groupId,
|
||||||
return _gridFFIService.createBoardCard(groupId);
|
{String? startRowId}) {
|
||||||
|
return _gridFFIService.createBoardCard(groupId, startRowId);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:app_flowy/core/grid_notification.dart';
|
||||||
|
import 'package:flowy_infra/notifier.dart';
|
||||||
|
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||||
|
import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
|
||||||
|
import 'package:dartz/dartz.dart';
|
||||||
|
import 'package:flowy_sdk/protobuf/flowy-grid/group_changeset.pb.dart';
|
||||||
|
|
||||||
|
typedef UpdateBoardNotifiedValue = Either<GroupViewChangesetPB, FlowyError>;
|
||||||
|
|
||||||
|
class BoardListener {
|
||||||
|
final String viewId;
|
||||||
|
PublishNotifier<UpdateBoardNotifiedValue>? _groupNotifier = PublishNotifier();
|
||||||
|
GridNotificationListener? _listener;
|
||||||
|
BoardListener(this.viewId);
|
||||||
|
|
||||||
|
void start({
|
||||||
|
required void Function(UpdateBoardNotifiedValue) onBoardChanged,
|
||||||
|
}) {
|
||||||
|
_groupNotifier?.addPublishListener(onBoardChanged);
|
||||||
|
_listener = GridNotificationListener(
|
||||||
|
objectId: viewId,
|
||||||
|
handler: _handler,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handler(
|
||||||
|
GridNotification ty,
|
||||||
|
Either<Uint8List, FlowyError> result,
|
||||||
|
) {
|
||||||
|
switch (ty) {
|
||||||
|
case GridNotification.DidUpdateGroupView:
|
||||||
|
result.fold(
|
||||||
|
(payload) => _groupNotifier?.value =
|
||||||
|
left(GroupViewChangesetPB.fromBuffer(payload)),
|
||||||
|
(error) => _groupNotifier?.value = right(error),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> stop() async {
|
||||||
|
await _listener?.stop();
|
||||||
|
_groupNotifier?.dispose();
|
||||||
|
_groupNotifier = null;
|
||||||
|
}
|
||||||
|
}
|
@ -79,7 +79,7 @@ class BoardDateCellState with _$BoardDateCellState {
|
|||||||
String _dateStrFromCellData(DateCellDataPB? cellData) {
|
String _dateStrFromCellData(DateCellDataPB? cellData) {
|
||||||
String dateStr = "";
|
String dateStr = "";
|
||||||
if (cellData != null) {
|
if (cellData != null) {
|
||||||
dateStr = cellData.date + " " + cellData.time;
|
dateStr = "${cellData.date} ${cellData.time}";
|
||||||
}
|
}
|
||||||
return dateStr;
|
return dateStr;
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,6 @@ class BoardSelectOptionCellState with _$BoardSelectOptionCellState {
|
|||||||
factory BoardSelectOptionCellState.initial(
|
factory BoardSelectOptionCellState.initial(
|
||||||
GridSelectOptionCellController context) {
|
GridSelectOptionCellController context) {
|
||||||
final data = context.getCellData();
|
final data = context.getCellData();
|
||||||
|
|
||||||
return BoardSelectOptionCellState(
|
return BoardSelectOptionCellState(
|
||||||
selectedOptions: data?.selectOptions ?? [],
|
selectedOptions: data?.selectOptions ?? [],
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||||
|
import 'package:flutter/foundation.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';
|
||||||
@ -20,6 +21,15 @@ class BoardTextCellBloc extends Bloc<BoardTextCellEvent, BoardTextCellState> {
|
|||||||
didReceiveCellUpdate: (content) {
|
didReceiveCellUpdate: (content) {
|
||||||
emit(state.copyWith(content: content));
|
emit(state.copyWith(content: content));
|
||||||
},
|
},
|
||||||
|
updateText: (text) {
|
||||||
|
if (text != state.content) {
|
||||||
|
cellController.saveCellData(text);
|
||||||
|
emit(state.copyWith(content: text));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enableEdit: (bool enabled) {
|
||||||
|
emit(state.copyWith(enableEdit: enabled));
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -49,6 +59,8 @@ class BoardTextCellBloc extends Bloc<BoardTextCellEvent, BoardTextCellState> {
|
|||||||
@freezed
|
@freezed
|
||||||
class BoardTextCellEvent with _$BoardTextCellEvent {
|
class BoardTextCellEvent with _$BoardTextCellEvent {
|
||||||
const factory BoardTextCellEvent.initial() = _InitialCell;
|
const factory BoardTextCellEvent.initial() = _InitialCell;
|
||||||
|
const factory BoardTextCellEvent.updateText(String text) = _UpdateContent;
|
||||||
|
const factory BoardTextCellEvent.enableEdit(bool enabled) = _EnableEdit;
|
||||||
const factory BoardTextCellEvent.didReceiveCellUpdate(String cellContent) =
|
const factory BoardTextCellEvent.didReceiveCellUpdate(String cellContent) =
|
||||||
_DidReceiveCellUpdate;
|
_DidReceiveCellUpdate;
|
||||||
}
|
}
|
||||||
@ -57,10 +69,12 @@ class BoardTextCellEvent with _$BoardTextCellEvent {
|
|||||||
class BoardTextCellState with _$BoardTextCellState {
|
class BoardTextCellState with _$BoardTextCellState {
|
||||||
const factory BoardTextCellState({
|
const factory BoardTextCellState({
|
||||||
required String content,
|
required String content,
|
||||||
|
required bool enableEdit,
|
||||||
}) = _BoardTextCellState;
|
}) = _BoardTextCellState;
|
||||||
|
|
||||||
factory BoardTextCellState.initial(GridCellController context) =>
|
factory BoardTextCellState.initial(GridCellController context) =>
|
||||||
BoardTextCellState(
|
BoardTextCellState(
|
||||||
content: context.getCellData() ?? "",
|
content: context.getCellData() ?? "",
|
||||||
|
enableEdit: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
|
|||||||
import 'package:app_flowy/plugins/grid/application/row/row_service.dart';
|
import 'package:app_flowy/plugins/grid/application/row/row_service.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
|
||||||
import 'package: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,10 +13,12 @@ import 'card_data_controller.dart';
|
|||||||
part 'card_bloc.freezed.dart';
|
part 'card_bloc.freezed.dart';
|
||||||
|
|
||||||
class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
|
class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
|
||||||
|
final String fieldId;
|
||||||
final RowFFIService _rowService;
|
final RowFFIService _rowService;
|
||||||
final CardDataController _dataController;
|
final CardDataController _dataController;
|
||||||
|
|
||||||
BoardCardBloc({
|
BoardCardBloc({
|
||||||
|
required this.fieldId,
|
||||||
required String gridId,
|
required String gridId,
|
||||||
required CardDataController dataController,
|
required CardDataController dataController,
|
||||||
}) : _rowService = RowFFIService(
|
}) : _rowService = RowFFIService(
|
||||||
@ -25,22 +26,22 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
|
|||||||
blockId: dataController.rowPB.blockId,
|
blockId: dataController.rowPB.blockId,
|
||||||
),
|
),
|
||||||
_dataController = dataController,
|
_dataController = dataController,
|
||||||
super(BoardCardState.initial(
|
super(
|
||||||
dataController.rowPB, dataController.loadData())) {
|
BoardCardState.initial(
|
||||||
|
dataController.rowPB,
|
||||||
|
_makeCells(fieldId, dataController.loadData()),
|
||||||
|
),
|
||||||
|
) {
|
||||||
on<BoardCardEvent>(
|
on<BoardCardEvent>(
|
||||||
(event, emit) async {
|
(event, emit) async {
|
||||||
await event.map(
|
await event.when(
|
||||||
initial: (_InitialRow value) async {
|
initial: () async {
|
||||||
await _startListening();
|
await _startListening();
|
||||||
},
|
},
|
||||||
didReceiveCells: (_DidReceiveCells value) async {
|
didReceiveCells: (cells, reason) async {
|
||||||
final cells = value.gridCellMap.values
|
|
||||||
.map((e) => GridCellEquatable(e.field))
|
|
||||||
.toList();
|
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
gridCellMap: value.gridCellMap,
|
cells: cells,
|
||||||
cells: UnmodifiableListView(cells),
|
changeReason: reason,
|
||||||
changeReason: value.reason,
|
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -58,7 +59,7 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
|
|||||||
return RowInfo(
|
return RowInfo(
|
||||||
gridId: _rowService.gridId,
|
gridId: _rowService.gridId,
|
||||||
fields: UnmodifiableListView(
|
fields: UnmodifiableListView(
|
||||||
state.cells.map((cell) => cell._field).toList(),
|
state.cells.map((cell) => cell.identifier.field).toList(),
|
||||||
),
|
),
|
||||||
rowPB: state.rowPB,
|
rowPB: state.rowPB,
|
||||||
);
|
);
|
||||||
@ -66,8 +67,9 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
|
|||||||
|
|
||||||
Future<void> _startListening() async {
|
Future<void> _startListening() async {
|
||||||
_dataController.addListener(
|
_dataController.addListener(
|
||||||
onRowChanged: (cells, reason) {
|
onRowChanged: (cellMap, reason) {
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
|
final cells = _makeCells(fieldId, cellMap);
|
||||||
add(BoardCardEvent.didReceiveCells(cells, reason));
|
add(BoardCardEvent.didReceiveCells(cells, reason));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -75,42 +77,52 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UnmodifiableListView<BoardCellEquatable> _makeCells(
|
||||||
|
String fieldId, GridCellMap originalCellMap) {
|
||||||
|
List<BoardCellEquatable> cells = [];
|
||||||
|
for (final entry in originalCellMap.entries) {
|
||||||
|
if (entry.value.fieldId != fieldId) {
|
||||||
|
cells.add(BoardCellEquatable(entry.value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return UnmodifiableListView(cells);
|
||||||
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class BoardCardEvent with _$BoardCardEvent {
|
class BoardCardEvent with _$BoardCardEvent {
|
||||||
const factory BoardCardEvent.initial() = _InitialRow;
|
const factory BoardCardEvent.initial() = _InitialRow;
|
||||||
const factory BoardCardEvent.didReceiveCells(
|
const factory BoardCardEvent.didReceiveCells(
|
||||||
GridCellMap gridCellMap, RowsChangedReason reason) = _DidReceiveCells;
|
UnmodifiableListView<BoardCellEquatable> cells,
|
||||||
|
RowsChangedReason reason,
|
||||||
|
) = _DidReceiveCells;
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class BoardCardState with _$BoardCardState {
|
class BoardCardState with _$BoardCardState {
|
||||||
const factory BoardCardState({
|
const factory BoardCardState({
|
||||||
required RowPB rowPB,
|
required RowPB rowPB,
|
||||||
required GridCellMap gridCellMap,
|
required UnmodifiableListView<BoardCellEquatable> cells,
|
||||||
required UnmodifiableListView<GridCellEquatable> cells,
|
|
||||||
RowsChangedReason? changeReason,
|
RowsChangedReason? changeReason,
|
||||||
}) = _BoardCardState;
|
}) = _BoardCardState;
|
||||||
|
|
||||||
factory BoardCardState.initial(RowPB rowPB, GridCellMap cellDataMap) =>
|
factory BoardCardState.initial(
|
||||||
|
RowPB rowPB, UnmodifiableListView<BoardCellEquatable> cells) =>
|
||||||
BoardCardState(
|
BoardCardState(
|
||||||
rowPB: rowPB,
|
rowPB: rowPB,
|
||||||
gridCellMap: cellDataMap,
|
cells: cells,
|
||||||
cells: UnmodifiableListView(
|
|
||||||
cellDataMap.values.map((e) => GridCellEquatable(e.field)).toList(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class GridCellEquatable extends Equatable {
|
class BoardCellEquatable extends Equatable {
|
||||||
final FieldPB _field;
|
final GridCellIdentifier identifier;
|
||||||
|
|
||||||
const GridCellEquatable(FieldPB field) : _field = field;
|
const BoardCellEquatable(this.identifier);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [
|
List<Object?> get props => [
|
||||||
_field.id,
|
identifier.field.id,
|
||||||
_field.fieldType,
|
identifier.field.fieldType,
|
||||||
_field.visibility,
|
identifier.field.visibility,
|
||||||
_field.width,
|
identifier.field.width,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import 'package:flowy_sdk/log.dart';
|
import 'package:flowy_sdk/log.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
|
import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
|
||||||
|
|
||||||
import 'group_listener.dart';
|
import 'group_listener.dart';
|
||||||
|
|
||||||
typedef OnGroupError = void Function(FlowyError);
|
typedef OnGroupError = void Function(FlowyError);
|
||||||
|
|
||||||
abstract class GroupControllerDelegate {
|
abstract class GroupControllerDelegate {
|
||||||
void removeRow(String groupId, String rowId);
|
void removeRow(GroupPB group, String rowId);
|
||||||
void insertRow(String groupId, RowPB row, int? index);
|
void insertRow(GroupPB group, RowPB row, int? index);
|
||||||
void updateRow(String groupId, RowPB row);
|
void updateRow(GroupPB group, RowPB row);
|
||||||
|
void addNewRow(GroupPB group, RowPB row, int? index);
|
||||||
}
|
}
|
||||||
|
|
||||||
class GroupController {
|
class GroupController {
|
||||||
@ -31,13 +31,22 @@ class GroupController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RowPB? lastRow() {
|
||||||
|
if (group.rows.isEmpty) return null;
|
||||||
|
return group.rows.last;
|
||||||
|
}
|
||||||
|
|
||||||
void startListening() {
|
void startListening() {
|
||||||
_listener.start(onGroupChanged: (result) {
|
_listener.start(onGroupChanged: (result) {
|
||||||
result.fold(
|
result.fold(
|
||||||
(GroupRowsChangesetPB changeset) {
|
(GroupChangesetPB changeset) {
|
||||||
|
for (final deletedRow in changeset.deletedRows) {
|
||||||
|
group.rows.removeWhere((rowPB) => rowPB.id == deletedRow);
|
||||||
|
delegate.removeRow(group, deletedRow);
|
||||||
|
}
|
||||||
|
|
||||||
for (final insertedRow in changeset.insertedRows) {
|
for (final insertedRow in changeset.insertedRows) {
|
||||||
final index = insertedRow.hasIndex() ? insertedRow.index : null;
|
final index = insertedRow.hasIndex() ? insertedRow.index : null;
|
||||||
|
|
||||||
if (insertedRow.hasIndex() &&
|
if (insertedRow.hasIndex() &&
|
||||||
group.rows.length > insertedRow.index) {
|
group.rows.length > insertedRow.index) {
|
||||||
group.rows.insert(insertedRow.index, insertedRow.row);
|
group.rows.insert(insertedRow.index, insertedRow.row);
|
||||||
@ -45,16 +54,11 @@ class GroupController {
|
|||||||
group.rows.add(insertedRow.row);
|
group.rows.add(insertedRow.row);
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate.insertRow(
|
if (insertedRow.isNew) {
|
||||||
group.groupId,
|
delegate.addNewRow(group, insertedRow.row, index);
|
||||||
insertedRow.row,
|
} else {
|
||||||
index,
|
delegate.insertRow(group, insertedRow.row, index);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final deletedRow in changeset.deletedRows) {
|
|
||||||
group.rows.removeWhere((rowPB) => rowPB.id == deletedRow);
|
|
||||||
delegate.removeRow(group.groupId, deletedRow);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final updatedRow in changeset.updatedRows) {
|
for (final updatedRow in changeset.updatedRows) {
|
||||||
@ -66,7 +70,7 @@ class GroupController {
|
|||||||
group.rows[index] = updatedRow;
|
group.rows[index] = updatedRow;
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate.updateRow(group.groupId, updatedRow);
|
delegate.updateRow(group, updatedRow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(err) => Log.error(err),
|
(err) => Log.error(err),
|
||||||
@ -74,6 +78,29 @@ class GroupController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GroupChangesetPB _transformChangeset(GroupChangesetPB changeset) {
|
||||||
|
// final insertedRows = changeset.insertedRows
|
||||||
|
// .where(
|
||||||
|
// (delete) => !changeset.deletedRows.contains(delete.row.id),
|
||||||
|
// )
|
||||||
|
// .toList();
|
||||||
|
|
||||||
|
// final deletedRows = changeset.deletedRows
|
||||||
|
// .where((deletedRowId) =>
|
||||||
|
// changeset.insertedRows
|
||||||
|
// .indexWhere((insert) => insert.row.id == deletedRowId) ==
|
||||||
|
// -1)
|
||||||
|
// .toList();
|
||||||
|
|
||||||
|
// return changeset.rebuild((rebuildChangeset) {
|
||||||
|
// rebuildChangeset.insertedRows.clear();
|
||||||
|
// rebuildChangeset.insertedRows.addAll(insertedRows);
|
||||||
|
|
||||||
|
// rebuildChangeset.deletedRows.clear();
|
||||||
|
// rebuildChangeset.deletedRows.addAll(deletedRows);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
_listener.stop();
|
_listener.stop();
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid/group.pb.dart';
|
|||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid/group_changeset.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-grid/group_changeset.pb.dart';
|
||||||
|
|
||||||
typedef UpdateGroupNotifiedValue = Either<GroupRowsChangesetPB, FlowyError>;
|
typedef UpdateGroupNotifiedValue = Either<GroupChangesetPB, FlowyError>;
|
||||||
|
|
||||||
class GroupListener {
|
class GroupListener {
|
||||||
final GroupPB group;
|
final GroupPB group;
|
||||||
@ -34,7 +34,7 @@ class GroupListener {
|
|||||||
case GridNotification.DidUpdateGroup:
|
case GridNotification.DidUpdateGroup:
|
||||||
result.fold(
|
result.fold(
|
||||||
(payload) => _groupNotifier?.value =
|
(payload) => _groupNotifier?.value =
|
||||||
left(GroupRowsChangesetPB.fromBuffer(payload)),
|
left(GroupChangesetPB.fromBuffer(payload)),
|
||||||
(error) => _groupNotifier?.value = right(error),
|
(error) => _groupNotifier?.value = right(error),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:dartz/dartz.dart';
|
||||||
|
|
||||||
|
part 'board_setting_bloc.freezed.dart';
|
||||||
|
|
||||||
|
class BoardSettingBloc extends Bloc<BoardSettingEvent, BoardSettingState> {
|
||||||
|
final String gridId;
|
||||||
|
BoardSettingBloc({required this.gridId})
|
||||||
|
: super(BoardSettingState.initial()) {
|
||||||
|
on<BoardSettingEvent>(
|
||||||
|
(event, emit) async {
|
||||||
|
event.when(performAction: (action) {
|
||||||
|
emit(state.copyWith(selectedAction: Some(action)));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() async {
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class BoardSettingEvent with _$BoardSettingEvent {
|
||||||
|
const factory BoardSettingEvent.performAction(BoardSettingAction action) =
|
||||||
|
_PerformAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class BoardSettingState with _$BoardSettingState {
|
||||||
|
const factory BoardSettingState({
|
||||||
|
required Option<BoardSettingAction> selectedAction,
|
||||||
|
}) = _BoardSettingState;
|
||||||
|
|
||||||
|
factory BoardSettingState.initial() => BoardSettingState(
|
||||||
|
selectedAction: none(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum BoardSettingAction {
|
||||||
|
properties,
|
||||||
|
}
|
@ -31,7 +31,7 @@ class BoardPluginBuilder implements PluginBuilder {
|
|||||||
|
|
||||||
class BoardPluginConfig implements PluginConfig {
|
class BoardPluginConfig implements PluginConfig {
|
||||||
@override
|
@override
|
||||||
bool get creatable => false;
|
bool get creatable => true;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BoardPlugin extends Plugin {
|
class BoardPlugin extends Plugin {
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
|
|
||||||
|
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||||
import 'package:app_flowy/plugins/board/application/card/card_data_controller.dart';
|
import 'package:app_flowy/plugins/board/application/card/card_data_controller.dart';
|
||||||
import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
|
import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
|
||||||
import 'package:app_flowy/plugins/grid/application/field/field_cache.dart';
|
import 'package:app_flowy/plugins/grid/application/field/field_cache.dart';
|
||||||
@ -9,16 +10,22 @@ import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart'
|
|||||||
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/cell_builder.dart';
|
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/cell_builder.dart';
|
||||||
import 'package:app_flowy/plugins/grid/presentation/widgets/row/row_detail.dart';
|
import 'package:app_flowy/plugins/grid/presentation/widgets/row/row_detail.dart';
|
||||||
import 'package:appflowy_board/appflowy_board.dart';
|
import 'package:appflowy_board/appflowy_board.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flowy_infra/image.dart';
|
||||||
|
import 'package:flowy_infra/theme.dart';
|
||||||
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
|
||||||
import 'package:flowy_infra_ui/widget/error_page.dart';
|
import 'package:flowy_infra_ui/widget/error_page.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
|
||||||
|
import 'package:flowy_sdk/protobuf/flowy-grid/group.pbserver.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 '../../grid/application/row/row_cache.dart';
|
import '../../grid/application/row/row_cache.dart';
|
||||||
import '../application/board_bloc.dart';
|
import '../application/board_bloc.dart';
|
||||||
import 'card/card.dart';
|
import 'card/card.dart';
|
||||||
import 'card/card_cell_builder.dart';
|
import 'card/card_cell_builder.dart';
|
||||||
|
import 'toolbar/board_toolbar.dart';
|
||||||
|
|
||||||
class BoardPage extends StatelessWidget {
|
class BoardPage extends StatelessWidget {
|
||||||
final ViewPB view;
|
final ViewPB view;
|
||||||
@ -30,13 +37,15 @@ class BoardPage extends StatelessWidget {
|
|||||||
create: (context) =>
|
create: (context) =>
|
||||||
BoardBloc(view: view)..add(const BoardEvent.initial()),
|
BoardBloc(view: view)..add(const BoardEvent.initial()),
|
||||||
child: BlocBuilder<BoardBloc, BoardState>(
|
child: BlocBuilder<BoardBloc, BoardState>(
|
||||||
|
buildWhen: (previous, current) =>
|
||||||
|
previous.loadingState != current.loadingState,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return state.loadingState.map(
|
return state.loadingState.map(
|
||||||
loading: (_) =>
|
loading: (_) =>
|
||||||
const Center(child: CircularProgressIndicator.adaptive()),
|
const Center(child: CircularProgressIndicator.adaptive()),
|
||||||
finish: (result) {
|
finish: (result) {
|
||||||
return result.successOrFail.fold(
|
return result.successOrFail.fold(
|
||||||
(_) => BoardContent(),
|
(_) => const BoardContent(),
|
||||||
(err) => FlowyErrorPage(err.toString()),
|
(err) => FlowyErrorPage(err.toString()),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -47,67 +56,172 @@ class BoardPage extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class BoardContent extends StatelessWidget {
|
class BoardContent extends StatefulWidget {
|
||||||
|
const BoardContent({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<BoardContent> createState() => _BoardContentState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BoardContentState extends State<BoardContent> {
|
||||||
|
late ScrollController scrollController;
|
||||||
|
late AFBoardScrollManager scrollManager;
|
||||||
|
|
||||||
final config = AFBoardConfig(
|
final config = AFBoardConfig(
|
||||||
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
|
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
|
||||||
);
|
);
|
||||||
|
|
||||||
BoardContent({Key? key}) : super(key: key);
|
@override
|
||||||
|
void initState() {
|
||||||
|
scrollController = ScrollController();
|
||||||
|
scrollManager = AFBoardScrollManager();
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<BoardBloc, BoardState>(
|
return BlocListener<BoardBloc, BoardState>(
|
||||||
|
listener: (context, state) => _handleEditState(state, context),
|
||||||
|
child: BlocBuilder<BoardBloc, BoardState>(
|
||||||
|
buildWhen: (previous, current) =>
|
||||||
|
previous.groupIds.length != current.groupIds.length,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
final theme = context.read<AppTheme>();
|
||||||
return Container(
|
return Container(
|
||||||
color: Colors.white,
|
color: theme.surface,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const _ToolbarBlocAdaptor(),
|
||||||
|
Expanded(
|
||||||
child: AFBoard(
|
child: AFBoard(
|
||||||
// key: UniqueKey(),
|
scrollManager: scrollManager,
|
||||||
scrollController: ScrollController(),
|
scrollController: scrollController,
|
||||||
dataController: context.read<BoardBloc>().afBoardDataController,
|
dataController: context.read<BoardBloc>().boardController,
|
||||||
headerBuilder: _buildHeader,
|
headerBuilder: _buildHeader,
|
||||||
footBuilder: _buildFooter,
|
footBuilder: _buildFooter,
|
||||||
cardBuilder: (_, data) => _buildCard(context, data),
|
cardBuilder: (_, column, columnItem) => _buildCard(
|
||||||
columnConstraints: const BoxConstraints.tightFor(width: 240),
|
context,
|
||||||
|
column,
|
||||||
|
columnItem,
|
||||||
|
),
|
||||||
|
columnConstraints:
|
||||||
|
const BoxConstraints.tightFor(width: 300),
|
||||||
config: AFBoardConfig(
|
config: AFBoardConfig(
|
||||||
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
|
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleEditState(BoardState state, BuildContext context) {
|
||||||
|
state.editingRow.fold(
|
||||||
|
() => null,
|
||||||
|
(editingRow) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (editingRow.index != null) {
|
||||||
|
context
|
||||||
|
.read<BoardBloc>()
|
||||||
|
.add(BoardEvent.endEditRow(editingRow.row.id));
|
||||||
|
} else {
|
||||||
|
scrollManager.scrollToBottom(editingRow.columnId, () {
|
||||||
|
context
|
||||||
|
.read<BoardBloc>()
|
||||||
|
.add(BoardEvent.endEditRow(editingRow.row.id));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildHeader(BuildContext context, AFBoardColumnData columnData) {
|
@override
|
||||||
|
void dispose() {
|
||||||
|
scrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHeader(
|
||||||
|
BuildContext context,
|
||||||
|
AFBoardColumnData columnData,
|
||||||
|
) {
|
||||||
return AppFlowyColumnHeader(
|
return AppFlowyColumnHeader(
|
||||||
icon: const Icon(Icons.lightbulb_circle),
|
title: Flexible(
|
||||||
title: Text(columnData.desc),
|
fit: FlexFit.tight,
|
||||||
addIcon: const Icon(Icons.add, size: 20),
|
child: FlowyText.medium(
|
||||||
moreIcon: const Icon(Icons.more_horiz, size: 20),
|
columnData.headerData.columnName,
|
||||||
|
fontSize: 14,
|
||||||
|
overflow: TextOverflow.clip,
|
||||||
|
color: context.read<AppTheme>().textColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
addIcon: SizedBox(
|
||||||
|
height: 20,
|
||||||
|
width: 20,
|
||||||
|
child: svgWidget(
|
||||||
|
"home/add",
|
||||||
|
color: context.read<AppTheme>().iconColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onAddButtonClick: () {
|
||||||
|
context.read<BoardBloc>().add(
|
||||||
|
BoardEvent.createHeaderRow(columnData.id),
|
||||||
|
);
|
||||||
|
},
|
||||||
height: 50,
|
height: 50,
|
||||||
margin: config.columnItemPadding,
|
margin: config.headerPadding,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildFooter(BuildContext context, AFBoardColumnData columnData) {
|
Widget _buildFooter(BuildContext context, AFBoardColumnData columnData) {
|
||||||
|
final group = columnData.customData as GroupPB;
|
||||||
|
if (group.isDefault) {
|
||||||
|
return const SizedBox();
|
||||||
|
} else {
|
||||||
return AppFlowyColumnFooter(
|
return AppFlowyColumnFooter(
|
||||||
icon: const Icon(Icons.add, size: 20),
|
icon: SizedBox(
|
||||||
title: const Text('New'),
|
height: 20,
|
||||||
|
width: 20,
|
||||||
|
child: svgWidget(
|
||||||
|
"home/add",
|
||||||
|
color: context.read<AppTheme>().iconColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
title: FlowyText.medium(
|
||||||
|
LocaleKeys.board_column_create_new_card.tr(),
|
||||||
|
fontSize: 14,
|
||||||
|
color: context.read<AppTheme>().textColor,
|
||||||
|
),
|
||||||
height: 50,
|
height: 50,
|
||||||
margin: config.columnItemPadding,
|
margin: config.footerPadding,
|
||||||
onAddButtonClick: () {
|
onAddButtonClick: () {
|
||||||
context.read<BoardBloc>().add(BoardEvent.createRow(columnData.id));
|
context.read<BoardBloc>().add(
|
||||||
});
|
BoardEvent.createBottomRow(columnData.id),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCard(BuildContext context, AFColumnItem item) {
|
Widget _buildCard(
|
||||||
final rowPB = (item as BoardColumnItem).row;
|
BuildContext context,
|
||||||
|
AFBoardColumnData column,
|
||||||
|
AFColumnItem columnItem,
|
||||||
|
) {
|
||||||
|
final boardColumnItem = columnItem as BoardColumnItem;
|
||||||
|
final rowPB = boardColumnItem.row;
|
||||||
final rowCache = context.read<BoardBloc>().getRowCache(rowPB.blockId);
|
final rowCache = context.read<BoardBloc>().getRowCache(rowPB.blockId);
|
||||||
|
|
||||||
/// Return placeholder widget if the rowCache is null.
|
/// Return placeholder widget if the rowCache is null.
|
||||||
if (rowCache == null) return SizedBox(key: ObjectKey(item));
|
if (rowCache == null) return SizedBox(key: ObjectKey(columnItem));
|
||||||
|
|
||||||
final fieldCache = context.read<BoardBloc>().fieldCache;
|
final fieldCache = context.read<BoardBloc>().fieldCache;
|
||||||
final gridId = context.read<BoardBloc>().gridId;
|
final gridId = context.read<BoardBloc>().gridId;
|
||||||
@ -118,21 +232,25 @@ class BoardContent extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final cellBuilder = BoardCellBuilder(cardController);
|
final cellBuilder = BoardCellBuilder(cardController);
|
||||||
final isEditing = context.read<BoardBloc>().state.editingRow.fold(
|
bool isEditing = false;
|
||||||
() => false,
|
context.read<BoardBloc>().state.editingRow.fold(
|
||||||
(editingRow) => editingRow.id == rowPB.id,
|
() => null,
|
||||||
|
(editingRow) {
|
||||||
|
isEditing = editingRow.row.id == columnItem.row.id;
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return AppFlowyColumnItemCard(
|
return AppFlowyColumnItemCard(
|
||||||
key: ObjectKey(item),
|
key: ValueKey(columnItem.id),
|
||||||
|
margin: config.cardPadding,
|
||||||
|
decoration: _makeBoxDecoration(context),
|
||||||
child: BoardCard(
|
child: BoardCard(
|
||||||
gridId: gridId,
|
gridId: gridId,
|
||||||
|
groupId: column.id,
|
||||||
|
fieldId: boardColumnItem.fieldId,
|
||||||
isEditing: isEditing,
|
isEditing: isEditing,
|
||||||
cellBuilder: cellBuilder,
|
cellBuilder: cellBuilder,
|
||||||
dataController: cardController,
|
dataController: cardController,
|
||||||
onEditEditing: (rowId) {
|
|
||||||
context.read<BoardBloc>().add(BoardEvent.endEditRow(rowId));
|
|
||||||
},
|
|
||||||
openCard: (context) => _openCard(
|
openCard: (context) => _openCard(
|
||||||
gridId,
|
gridId,
|
||||||
fieldCache,
|
fieldCache,
|
||||||
@ -144,6 +262,16 @@ class BoardContent extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BoxDecoration _makeBoxDecoration(BuildContext context) {
|
||||||
|
final theme = context.read<AppTheme>();
|
||||||
|
final borderSide = BorderSide(color: theme.shader6, width: 1.0);
|
||||||
|
return BoxDecoration(
|
||||||
|
color: theme.surface,
|
||||||
|
border: Border.fromBorderSide(borderSide),
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(6)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void _openCard(String gridId, GridFieldCache fieldCache, RowPB rowPB,
|
void _openCard(String gridId, GridFieldCache fieldCache, RowPB rowPB,
|
||||||
GridRowCache rowCache, BuildContext context) {
|
GridRowCache rowCache, BuildContext context) {
|
||||||
final rowInfo = RowInfo(
|
final rowInfo = RowInfo(
|
||||||
@ -165,7 +293,27 @@ class BoardContent extends StatelessWidget {
|
|||||||
cellBuilder: GridCellBuilder(delegate: dataController),
|
cellBuilder: GridCellBuilder(delegate: dataController),
|
||||||
dataController: dataController,
|
dataController: dataController,
|
||||||
);
|
);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ToolbarBlocAdaptor extends StatelessWidget {
|
||||||
|
const _ToolbarBlocAdaptor({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<BoardBloc, BoardState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
final bloc = context.read<BoardBloc>();
|
||||||
|
final toolbarContext = BoardToolbarContext(
|
||||||
|
viewId: bloc.gridId,
|
||||||
|
fieldCache: bloc.fieldCache,
|
||||||
|
);
|
||||||
|
|
||||||
|
return BoardToolbar(toolbarContext: toolbarContext);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,62 @@
|
|||||||
|
import 'package:app_flowy/plugins/grid/application/prelude.dart';
|
||||||
|
import 'package:flowy_infra/notifier.dart';
|
||||||
|
|
||||||
|
abstract class FocusableBoardCell {
|
||||||
|
set becomeFocus(bool isFocus);
|
||||||
|
}
|
||||||
|
|
||||||
|
class EditableCellNotifier {
|
||||||
|
final Notifier becomeFirstResponder = Notifier();
|
||||||
|
|
||||||
|
final Notifier resignFirstResponder = Notifier();
|
||||||
|
|
||||||
|
EditableCellNotifier();
|
||||||
|
}
|
||||||
|
|
||||||
|
class EditableRowNotifier {
|
||||||
|
Map<EditableCellId, EditableCellNotifier> cells = {};
|
||||||
|
|
||||||
|
void insertCell(
|
||||||
|
GridCellIdentifier cellIdentifier,
|
||||||
|
EditableCellNotifier notifier,
|
||||||
|
) {
|
||||||
|
cells[EditableCellId.from(cellIdentifier)] = notifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
void becomeFirstResponder() {
|
||||||
|
for (final notifier in cells.values) {
|
||||||
|
notifier.becomeFirstResponder.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void resignFirstResponder() {
|
||||||
|
for (final notifier in cells.values) {
|
||||||
|
notifier.resignFirstResponder.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
for (final notifier in cells.values) {
|
||||||
|
notifier.resignFirstResponder.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
cells.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class EditableCell {
|
||||||
|
EditableCellNotifier? get editableNotifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
class EditableCellId {
|
||||||
|
String fieldId;
|
||||||
|
String rowId;
|
||||||
|
|
||||||
|
EditableCellId(this.rowId, this.fieldId);
|
||||||
|
|
||||||
|
factory EditableCellId.from(GridCellIdentifier cellIdentifier) =>
|
||||||
|
EditableCellId(
|
||||||
|
cellIdentifier.rowId,
|
||||||
|
cellIdentifier.fieldId,
|
||||||
|
);
|
||||||
|
}
|
@ -6,9 +6,11 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class BoardCheckboxCell extends StatefulWidget {
|
class BoardCheckboxCell extends StatefulWidget {
|
||||||
|
final String groupId;
|
||||||
final GridCellControllerBuilder cellControllerBuilder;
|
final GridCellControllerBuilder cellControllerBuilder;
|
||||||
|
|
||||||
const BoardCheckboxCell({
|
const BoardCheckboxCell({
|
||||||
|
required this.groupId,
|
||||||
required this.cellControllerBuilder,
|
required this.cellControllerBuilder,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
@ -34,6 +36,8 @@ class _BoardCheckboxCellState extends State<BoardCheckboxCell> {
|
|||||||
return BlocProvider.value(
|
return BlocProvider.value(
|
||||||
value: _cellBloc,
|
value: _cellBloc,
|
||||||
child: BlocBuilder<BoardCheckboxCellBloc, BoardCheckboxCellState>(
|
child: BlocBuilder<BoardCheckboxCellBloc, BoardCheckboxCellState>(
|
||||||
|
buildWhen: (previous, current) =>
|
||||||
|
previous.isSelected != current.isSelected,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final icon = state.isSelected
|
final icon = state.isSelected
|
||||||
? svgWidget('editor/editor_check')
|
? svgWidget('editor/editor_check')
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
import 'package:app_flowy/plugins/board/application/card/board_date_cell_bloc.dart';
|
import 'package:app_flowy/plugins/board/application/card/board_date_cell_bloc.dart';
|
||||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||||
|
import 'package:flowy_infra/theme.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
import 'package:flowy_infra_ui/style_widget/text.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 BoardDateCell extends StatefulWidget {
|
class BoardDateCell extends StatefulWidget {
|
||||||
|
final String groupId;
|
||||||
final GridCellControllerBuilder cellControllerBuilder;
|
final GridCellControllerBuilder cellControllerBuilder;
|
||||||
|
|
||||||
const BoardDateCell({
|
const BoardDateCell({
|
||||||
|
required this.groupId,
|
||||||
required this.cellControllerBuilder,
|
required this.cellControllerBuilder,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
@ -34,6 +37,7 @@ class _BoardDateCellState extends State<BoardDateCell> {
|
|||||||
return BlocProvider.value(
|
return BlocProvider.value(
|
||||||
value: _cellBloc,
|
value: _cellBloc,
|
||||||
child: BlocBuilder<BoardDateCellBloc, BoardDateCellState>(
|
child: BlocBuilder<BoardDateCellBloc, BoardDateCellState>(
|
||||||
|
buildWhen: (previous, current) => previous.dateStr != current.dateStr,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state.dateStr.isEmpty) {
|
if (state.dateStr.isEmpty) {
|
||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
@ -42,7 +46,8 @@ class _BoardDateCellState extends State<BoardDateCell> {
|
|||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: FlowyText.regular(
|
child: FlowyText.regular(
|
||||||
state.dateStr,
|
state.dateStr,
|
||||||
fontSize: 14,
|
fontSize: 13,
|
||||||
|
color: context.read<AppTheme>().shader3,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,11 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class BoardNumberCell extends StatefulWidget {
|
class BoardNumberCell extends StatefulWidget {
|
||||||
|
final String groupId;
|
||||||
final GridCellControllerBuilder cellControllerBuilder;
|
final GridCellControllerBuilder cellControllerBuilder;
|
||||||
|
|
||||||
const BoardNumberCell({
|
const BoardNumberCell({
|
||||||
|
required this.groupId,
|
||||||
required this.cellControllerBuilder,
|
required this.cellControllerBuilder,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
@ -34,13 +36,14 @@ class _BoardNumberCellState extends State<BoardNumberCell> {
|
|||||||
return BlocProvider.value(
|
return BlocProvider.value(
|
||||||
value: _cellBloc,
|
value: _cellBloc,
|
||||||
child: BlocBuilder<BoardNumberCellBloc, BoardNumberCellState>(
|
child: BlocBuilder<BoardNumberCellBloc, BoardNumberCellState>(
|
||||||
|
buildWhen: (previous, current) => previous.content != current.content,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state.content.isEmpty) {
|
if (state.content.isEmpty) {
|
||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
} else {
|
} else {
|
||||||
return Align(
|
return Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: FlowyText.regular(
|
child: FlowyText.medium(
|
||||||
state.content,
|
state.content,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
),
|
),
|
||||||
|
@ -1,14 +1,22 @@
|
|||||||
import 'package:app_flowy/plugins/board/application/card/board_select_option_cell_bloc.dart';
|
import 'package:app_flowy/plugins/board/application/card/board_select_option_cell_bloc.dart';
|
||||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||||
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/extension.dart';
|
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/extension.dart';
|
||||||
|
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_editor.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 BoardSelectOptionCell extends StatefulWidget {
|
import 'board_cell.dart';
|
||||||
|
|
||||||
|
class BoardSelectOptionCell extends StatefulWidget with EditableCell {
|
||||||
|
final String groupId;
|
||||||
final GridCellControllerBuilder cellControllerBuilder;
|
final GridCellControllerBuilder cellControllerBuilder;
|
||||||
|
@override
|
||||||
|
final EditableCellNotifier? editableNotifier;
|
||||||
|
|
||||||
const BoardSelectOptionCell({
|
const BoardSelectOptionCell({
|
||||||
|
required this.groupId,
|
||||||
required this.cellControllerBuilder,
|
required this.cellControllerBuilder,
|
||||||
|
this.editableNotifier,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@ -33,23 +41,41 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
|
|||||||
return BlocProvider.value(
|
return BlocProvider.value(
|
||||||
value: _cellBloc,
|
value: _cellBloc,
|
||||||
child: BlocBuilder<BoardSelectOptionCellBloc, BoardSelectOptionCellState>(
|
child: BlocBuilder<BoardSelectOptionCellBloc, BoardSelectOptionCellState>(
|
||||||
|
buildWhen: (previous, current) {
|
||||||
|
return previous.selectedOptions != current.selectedOptions;
|
||||||
|
},
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
if (state.selectedOptions
|
||||||
|
.where((element) => element.id == widget.groupId)
|
||||||
|
.isNotEmpty ||
|
||||||
|
state.selectedOptions.isEmpty) {
|
||||||
|
return const SizedBox();
|
||||||
|
} else {
|
||||||
final children = state.selectedOptions
|
final children = state.selectedOptions
|
||||||
.map((option) => SelectOptionTag.fromOption(
|
.map(
|
||||||
|
(option) => SelectOptionTag.fromOption(
|
||||||
context: context,
|
context: context,
|
||||||
option: option,
|
option: option,
|
||||||
))
|
|
||||||
.toList();
|
|
||||||
return Align(
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: AbsorbPointer(
|
|
||||||
child: Wrap(
|
|
||||||
children: children,
|
|
||||||
spacing: 4,
|
|
||||||
runSpacing: 2,
|
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
return IntrinsicHeight(
|
||||||
|
child: Stack(
|
||||||
|
alignment: AlignmentDirectional.center,
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||||
|
child: Wrap(spacing: 4, runSpacing: 2, children: children),
|
||||||
|
),
|
||||||
|
_SelectOptionDialog(
|
||||||
|
controller: widget.cellControllerBuilder.build(),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -61,3 +87,23 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _SelectOptionDialog extends StatelessWidget {
|
||||||
|
final GridSelectOptionCellController _controller;
|
||||||
|
const _SelectOptionDialog({
|
||||||
|
Key? key,
|
||||||
|
required IGridCellController controller,
|
||||||
|
}) : _controller = controller as GridSelectOptionCellController,
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return InkWell(onTap: () {
|
||||||
|
SelectOptionCellEditor.show(
|
||||||
|
context,
|
||||||
|
_controller,
|
||||||
|
() {},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,13 +1,27 @@
|
|||||||
import 'package:app_flowy/plugins/board/application/card/board_text_cell_bloc.dart';
|
import 'package:app_flowy/plugins/board/application/card/board_text_cell_bloc.dart';
|
||||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||||
|
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/cell_builder.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
import 'package:flowy_infra_ui/style_widget/text.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 BoardTextCell extends StatefulWidget {
|
import 'board_cell.dart';
|
||||||
|
import 'define.dart';
|
||||||
|
|
||||||
|
class BoardTextCell extends StatefulWidget with EditableCell {
|
||||||
|
final String groupId;
|
||||||
|
final bool isFocus;
|
||||||
|
@override
|
||||||
|
final EditableCellNotifier? editableNotifier;
|
||||||
final GridCellControllerBuilder cellControllerBuilder;
|
final GridCellControllerBuilder cellControllerBuilder;
|
||||||
const BoardTextCell({required this.cellControllerBuilder, Key? key})
|
|
||||||
: super(key: key);
|
const BoardTextCell({
|
||||||
|
required this.groupId,
|
||||||
|
required this.cellControllerBuilder,
|
||||||
|
this.editableNotifier,
|
||||||
|
this.isFocus = false,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<BoardTextCell> createState() => _BoardTextCellState();
|
State<BoardTextCell> createState() => _BoardTextCellState();
|
||||||
@ -15,14 +29,48 @@ class BoardTextCell extends StatefulWidget {
|
|||||||
|
|
||||||
class _BoardTextCellState extends State<BoardTextCell> {
|
class _BoardTextCellState extends State<BoardTextCell> {
|
||||||
late BoardTextCellBloc _cellBloc;
|
late BoardTextCellBloc _cellBloc;
|
||||||
|
late TextEditingController _controller;
|
||||||
|
bool focusWhenInit = false;
|
||||||
|
SingleListenerFocusNode focusNode = SingleListenerFocusNode();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
final cellController =
|
final cellController =
|
||||||
widget.cellControllerBuilder.build() as GridCellController;
|
widget.cellControllerBuilder.build() as GridCellController;
|
||||||
|
|
||||||
_cellBloc = BoardTextCellBloc(cellController: cellController)
|
_cellBloc = BoardTextCellBloc(cellController: cellController)
|
||||||
..add(const BoardTextCellEvent.initial());
|
..add(const BoardTextCellEvent.initial());
|
||||||
|
_controller = TextEditingController(text: _cellBloc.state.content);
|
||||||
|
focusWhenInit = widget.isFocus;
|
||||||
|
|
||||||
|
if (widget.isFocus) {
|
||||||
|
focusNode.requestFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
focusNode.addListener(() {
|
||||||
|
if (!focusNode.hasFocus) {
|
||||||
|
_cellBloc.add(const BoardTextCellEvent.enableEdit(false));
|
||||||
|
|
||||||
|
if (focusWhenInit) {
|
||||||
|
setState(() {
|
||||||
|
focusWhenInit = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
widget.editableNotifier?.becomeFirstResponder.addListener(() {
|
||||||
|
if (!mounted) return;
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
focusNode.requestFocus();
|
||||||
|
});
|
||||||
|
_cellBloc.add(const BoardTextCellEvent.enableEdit(true));
|
||||||
|
});
|
||||||
|
|
||||||
|
widget.editableNotifier?.resignFirstResponder.addListener(() {
|
||||||
|
if (!mounted) return;
|
||||||
|
_cellBloc.add(const BoardTextCellEvent.enableEdit(false));
|
||||||
|
});
|
||||||
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,32 +78,75 @@ class _BoardTextCellState extends State<BoardTextCell> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider.value(
|
return BlocProvider.value(
|
||||||
value: _cellBloc,
|
value: _cellBloc,
|
||||||
|
child: BlocListener<BoardTextCellBloc, BoardTextCellState>(
|
||||||
|
listener: (context, state) {
|
||||||
|
if (_controller.text != state.content) {
|
||||||
|
_controller.text = state.content;
|
||||||
|
}
|
||||||
|
},
|
||||||
child: BlocBuilder<BoardTextCellBloc, BoardTextCellState>(
|
child: BlocBuilder<BoardTextCellBloc, BoardTextCellState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state.content.isEmpty) {
|
if (state.content.isEmpty &&
|
||||||
|
state.enableEdit == false &&
|
||||||
|
focusWhenInit == false) {
|
||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
Widget child;
|
||||||
|
if (state.enableEdit || focusWhenInit) {
|
||||||
|
child = _buildTextField();
|
||||||
} else {
|
} else {
|
||||||
return Align(
|
child = _buildText(state);
|
||||||
alignment: Alignment.centerLeft,
|
}
|
||||||
child: ConstrainedBox(
|
return Align(alignment: Alignment.centerLeft, child: child);
|
||||||
constraints: BoxConstraints.loose(
|
},
|
||||||
const Size(double.infinity, 100),
|
|
||||||
),
|
|
||||||
child: FlowyText.regular(
|
|
||||||
state.content,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
),
|
Future<void> focusChanged() async {
|
||||||
);
|
_cellBloc.add(BoardTextCellEvent.updateText(_controller.text));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
_cellBloc.close();
|
_cellBloc.close();
|
||||||
|
_controller.dispose();
|
||||||
|
focusNode.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildText(BoardTextCellState state) {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
vertical: BoardSizes.cardCellVPadding,
|
||||||
|
),
|
||||||
|
child: FlowyText.medium(state.content, fontSize: 14),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTextField() {
|
||||||
|
return TextField(
|
||||||
|
controller: _controller,
|
||||||
|
focusNode: focusNode,
|
||||||
|
onChanged: (value) => focusChanged(),
|
||||||
|
onEditingComplete: () => focusNode.unfocus(),
|
||||||
|
maxLines: 1,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
fontFamily: 'Mulish',
|
||||||
|
),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
// Magic number 4 makes the textField take up the same space as FlowyText
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
vertical: BoardSizes.cardCellVPadding + 4,
|
||||||
|
),
|
||||||
|
border: InputBorder.none,
|
||||||
|
isDense: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,11 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class BoardUrlCell extends StatefulWidget {
|
class BoardUrlCell extends StatefulWidget {
|
||||||
|
final String groupId;
|
||||||
final GridCellControllerBuilder cellControllerBuilder;
|
final GridCellControllerBuilder cellControllerBuilder;
|
||||||
|
|
||||||
const BoardUrlCell({
|
const BoardUrlCell({
|
||||||
|
required this.groupId,
|
||||||
required this.cellControllerBuilder,
|
required this.cellControllerBuilder,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
@ -34,6 +36,7 @@ class _BoardUrlCellState extends State<BoardUrlCell> {
|
|||||||
return BlocProvider.value(
|
return BlocProvider.value(
|
||||||
value: _cellBloc,
|
value: _cellBloc,
|
||||||
child: BlocBuilder<BoardURLCellBloc, BoardURLCellState>(
|
child: BlocBuilder<BoardURLCellBloc, BoardURLCellState>(
|
||||||
|
buildWhen: (previous, current) => previous.content != current.content,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state.content.isEmpty) {
|
if (state.content.isEmpty) {
|
||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
|
@ -7,25 +7,26 @@ import 'package:flowy_infra/theme.dart';
|
|||||||
import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui_web.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 'board_cell.dart';
|
||||||
import 'card_cell_builder.dart';
|
import 'card_cell_builder.dart';
|
||||||
import 'card_container.dart';
|
import 'card_container.dart';
|
||||||
|
|
||||||
typedef OnEndEditing = void Function(String rowId);
|
|
||||||
|
|
||||||
class BoardCard extends StatefulWidget {
|
class BoardCard extends StatefulWidget {
|
||||||
final String gridId;
|
final String gridId;
|
||||||
|
final String groupId;
|
||||||
|
final String fieldId;
|
||||||
final bool isEditing;
|
final bool isEditing;
|
||||||
final CardDataController dataController;
|
final CardDataController dataController;
|
||||||
final BoardCellBuilder cellBuilder;
|
final BoardCellBuilder cellBuilder;
|
||||||
final OnEndEditing onEditEditing;
|
|
||||||
final void Function(BuildContext) openCard;
|
final void Function(BuildContext) openCard;
|
||||||
|
|
||||||
const BoardCard({
|
const BoardCard({
|
||||||
required this.gridId,
|
required this.gridId,
|
||||||
|
required this.groupId,
|
||||||
|
required this.fieldId,
|
||||||
required this.isEditing,
|
required this.isEditing,
|
||||||
required this.dataController,
|
required this.dataController,
|
||||||
required this.cellBuilder,
|
required this.cellBuilder,
|
||||||
required this.onEditEditing,
|
|
||||||
required this.openCard,
|
required this.openCard,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
@ -36,13 +37,16 @@ class BoardCard extends StatefulWidget {
|
|||||||
|
|
||||||
class _BoardCardState extends State<BoardCard> {
|
class _BoardCardState extends State<BoardCard> {
|
||||||
late BoardCardBloc _cardBloc;
|
late BoardCardBloc _cardBloc;
|
||||||
|
late EditableRowNotifier rowNotifier;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
rowNotifier = EditableRowNotifier();
|
||||||
_cardBloc = BoardCardBloc(
|
_cardBloc = BoardCardBloc(
|
||||||
gridId: widget.gridId,
|
gridId: widget.gridId,
|
||||||
|
fieldId: widget.fieldId,
|
||||||
dataController: widget.dataController,
|
dataController: widget.dataController,
|
||||||
);
|
)..add(const BoardCardEvent.initial());
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,16 +55,28 @@ class _BoardCardState extends State<BoardCard> {
|
|||||||
return BlocProvider.value(
|
return BlocProvider.value(
|
||||||
value: _cardBloc,
|
value: _cardBloc,
|
||||||
child: BlocBuilder<BoardCardBloc, BoardCardState>(
|
child: BlocBuilder<BoardCardBloc, BoardCardState>(
|
||||||
|
buildWhen: (previous, current) {
|
||||||
|
return previous.cells.length != current.cells.length;
|
||||||
|
},
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return BoardCardContainer(
|
return BoardCardContainer(
|
||||||
accessoryBuilder: (context) {
|
accessoryBuilder: (context) {
|
||||||
return [const _CardMoreOption()];
|
return [
|
||||||
|
_CardEditOption(
|
||||||
|
startEditing: () => rowNotifier.becomeFirstResponder(),
|
||||||
|
),
|
||||||
|
const _CardMoreOption(),
|
||||||
|
];
|
||||||
},
|
},
|
||||||
onTap: (context) {
|
onTap: (context) {
|
||||||
widget.openCard(context);
|
widget.openCard(context);
|
||||||
},
|
},
|
||||||
child: Column(
|
child: Column(
|
||||||
children: _makeCells(context, state.gridCellMap),
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: _makeCells(
|
||||||
|
context,
|
||||||
|
state.cells.map((cell) => cell.identifier).toList(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -68,16 +84,42 @@ class _BoardCardState extends State<BoardCard> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _makeCells(BuildContext context, GridCellMap cellMap) {
|
List<Widget> _makeCells(
|
||||||
return cellMap.values.map(
|
BuildContext context,
|
||||||
(cellId) {
|
List<GridCellIdentifier> cells,
|
||||||
final child = widget.cellBuilder.buildCell(cellId);
|
) {
|
||||||
return Padding(
|
final List<Widget> children = [];
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5),
|
cells.asMap().forEach(
|
||||||
|
(int index, GridCellIdentifier cellId) {
|
||||||
|
final cellNotifier = EditableCellNotifier();
|
||||||
|
Widget child = widget.cellBuilder.buildCell(
|
||||||
|
widget.groupId,
|
||||||
|
cellId,
|
||||||
|
widget.isEditing,
|
||||||
|
cellNotifier,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (index == 0) {
|
||||||
|
rowNotifier.insertCell(cellId, cellNotifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
child = Padding(
|
||||||
|
key: cellId.key(),
|
||||||
|
padding: const EdgeInsets.only(left: 4, right: 4),
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
children.add(child);
|
||||||
},
|
},
|
||||||
).toList();
|
);
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> dispose() async {
|
||||||
|
rowNotifier.dispose();
|
||||||
|
_cardBloc.close();
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,7 +128,11 @@ class _CardMoreOption extends StatelessWidget with CardAccessory {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return svgWidget('home/details', color: context.read<AppTheme>().iconColor);
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(3.0),
|
||||||
|
child:
|
||||||
|
svgWidget('grid/details', color: context.read<AppTheme>().iconColor),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -96,3 +142,27 @@ class _CardMoreOption extends StatelessWidget with CardAccessory {
|
|||||||
).show(context, direction: AnchorDirection.bottomWithCenterAligned);
|
).show(context, direction: AnchorDirection.bottomWithCenterAligned);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _CardEditOption extends StatelessWidget with CardAccessory {
|
||||||
|
final VoidCallback startEditing;
|
||||||
|
const _CardEditOption({
|
||||||
|
required this.startEditing,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(3.0),
|
||||||
|
child: svgWidget(
|
||||||
|
'editor/edit',
|
||||||
|
color: context.read<AppTheme>().iconColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onTap(BuildContext context) {
|
||||||
|
startEditing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,6 +2,7 @@ import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_servic
|
|||||||
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/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'board_cell.dart';
|
||||||
import 'board_checkbox_cell.dart';
|
import 'board_checkbox_cell.dart';
|
||||||
import 'board_date_cell.dart';
|
import 'board_date_cell.dart';
|
||||||
import 'board_number_cell.dart';
|
import 'board_number_cell.dart';
|
||||||
@ -19,7 +20,12 @@ class BoardCellBuilder {
|
|||||||
|
|
||||||
BoardCellBuilder(this.delegate);
|
BoardCellBuilder(this.delegate);
|
||||||
|
|
||||||
Widget buildCell(GridCellIdentifier cellId) {
|
Widget buildCell(
|
||||||
|
String groupId,
|
||||||
|
GridCellIdentifier cellId,
|
||||||
|
bool isEditing,
|
||||||
|
EditableCellNotifier cellNotifier,
|
||||||
|
) {
|
||||||
final cellControllerBuilder = GridCellControllerBuilder(
|
final cellControllerBuilder = GridCellControllerBuilder(
|
||||||
delegate: delegate,
|
delegate: delegate,
|
||||||
cellId: cellId,
|
cellId: cellId,
|
||||||
@ -30,36 +36,46 @@ class BoardCellBuilder {
|
|||||||
switch (cellId.fieldType) {
|
switch (cellId.fieldType) {
|
||||||
case FieldType.Checkbox:
|
case FieldType.Checkbox:
|
||||||
return BoardCheckboxCell(
|
return BoardCheckboxCell(
|
||||||
|
groupId: groupId,
|
||||||
cellControllerBuilder: cellControllerBuilder,
|
cellControllerBuilder: cellControllerBuilder,
|
||||||
key: key,
|
key: key,
|
||||||
);
|
);
|
||||||
case FieldType.DateTime:
|
case FieldType.DateTime:
|
||||||
return BoardDateCell(
|
return BoardDateCell(
|
||||||
|
groupId: groupId,
|
||||||
cellControllerBuilder: cellControllerBuilder,
|
cellControllerBuilder: cellControllerBuilder,
|
||||||
key: key,
|
key: key,
|
||||||
);
|
);
|
||||||
case FieldType.SingleSelect:
|
case FieldType.SingleSelect:
|
||||||
return BoardSelectOptionCell(
|
return BoardSelectOptionCell(
|
||||||
|
groupId: groupId,
|
||||||
cellControllerBuilder: cellControllerBuilder,
|
cellControllerBuilder: cellControllerBuilder,
|
||||||
key: key,
|
key: key,
|
||||||
);
|
);
|
||||||
case FieldType.MultiSelect:
|
case FieldType.MultiSelect:
|
||||||
return BoardSelectOptionCell(
|
return BoardSelectOptionCell(
|
||||||
|
groupId: groupId,
|
||||||
cellControllerBuilder: cellControllerBuilder,
|
cellControllerBuilder: cellControllerBuilder,
|
||||||
|
editableNotifier: cellNotifier,
|
||||||
key: key,
|
key: key,
|
||||||
);
|
);
|
||||||
case FieldType.Number:
|
case FieldType.Number:
|
||||||
return BoardNumberCell(
|
return BoardNumberCell(
|
||||||
|
groupId: groupId,
|
||||||
cellControllerBuilder: cellControllerBuilder,
|
cellControllerBuilder: cellControllerBuilder,
|
||||||
key: key,
|
key: key,
|
||||||
);
|
);
|
||||||
case FieldType.RichText:
|
case FieldType.RichText:
|
||||||
return BoardTextCell(
|
return BoardTextCell(
|
||||||
|
groupId: groupId,
|
||||||
cellControllerBuilder: cellControllerBuilder,
|
cellControllerBuilder: cellControllerBuilder,
|
||||||
|
isFocus: isEditing,
|
||||||
|
editableNotifier: cellNotifier,
|
||||||
key: key,
|
key: key,
|
||||||
);
|
);
|
||||||
case FieldType.URL:
|
case FieldType.URL:
|
||||||
return BoardUrlCell(
|
return BoardUrlCell(
|
||||||
|
groupId: groupId,
|
||||||
cellControllerBuilder: cellControllerBuilder,
|
cellControllerBuilder: cellControllerBuilder,
|
||||||
key: key,
|
key: key,
|
||||||
);
|
);
|
||||||
|
@ -26,8 +26,8 @@ class BoardCardContainer extends StatelessWidget {
|
|||||||
final accessories = accessoryBuilder!(context);
|
final accessories = accessoryBuilder!(context);
|
||||||
if (accessories.isNotEmpty) {
|
if (accessories.isNotEmpty) {
|
||||||
container = _CardEnterRegion(
|
container = _CardEnterRegion(
|
||||||
child: container,
|
|
||||||
accessories: accessories,
|
accessories: accessories,
|
||||||
|
child: container,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -69,25 +69,48 @@ class CardAccessoryContainer extends StatelessWidget {
|
|||||||
style: HoverStyle(
|
style: HoverStyle(
|
||||||
hoverColor: theme.hover,
|
hoverColor: theme.hover,
|
||||||
backgroundColor: theme.surface,
|
backgroundColor: theme.surface,
|
||||||
|
borderRadius: BorderRadius.zero,
|
||||||
),
|
),
|
||||||
builder: (_, onHover) => Container(
|
builder: (_, onHover) => SizedBox(
|
||||||
width: 26,
|
width: 24,
|
||||||
height: 26,
|
height: 24,
|
||||||
padding: const EdgeInsets.all(3),
|
|
||||||
child: accessory,
|
child: accessory,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
child: hover,
|
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onTap: () => accessory.onTap(context),
|
onTap: () => accessory.onTap(context),
|
||||||
|
child: hover,
|
||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
return Wrap(children: children, spacing: 6);
|
return Container(
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
decoration: _makeBoxDecoration(context),
|
||||||
|
child: Row(children: children),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BoxDecoration _makeBoxDecoration(BuildContext context) {
|
||||||
|
final theme = context.read<AppTheme>();
|
||||||
|
final borderSide = BorderSide(color: theme.shader6, width: 1.0);
|
||||||
|
return BoxDecoration(
|
||||||
|
color: Colors.transparent,
|
||||||
|
border: Border.fromBorderSide(borderSide),
|
||||||
|
// boxShadow: const [
|
||||||
|
// BoxShadow(
|
||||||
|
// color: Colors.transparent,
|
||||||
|
// spreadRadius: 0,
|
||||||
|
// blurRadius: 5,
|
||||||
|
// offset: Offset.zero,
|
||||||
|
// )
|
||||||
|
// ],
|
||||||
|
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
class _CardEnterRegion extends StatelessWidget {
|
class _CardEnterRegion extends StatelessWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
final List<CardAccessory> accessories;
|
final List<CardAccessory> accessories;
|
||||||
@ -102,8 +125,9 @@ class _CardEnterRegion extends StatelessWidget {
|
|||||||
builder: (context, onEnter, _) {
|
builder: (context, onEnter, _) {
|
||||||
List<Widget> children = [child];
|
List<Widget> children = [child];
|
||||||
if (onEnter) {
|
if (onEnter) {
|
||||||
children.add(CardAccessoryContainer(accessories: accessories)
|
children.add(CardAccessoryContainer(
|
||||||
.positioned(right: 0));
|
accessories: accessories,
|
||||||
|
).positioned(right: 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
return MouseRegion(
|
return MouseRegion(
|
||||||
@ -116,7 +140,7 @@ class _CardEnterRegion extends StatelessWidget {
|
|||||||
.onEnter = false,
|
.onEnter = false,
|
||||||
child: IntrinsicHeight(
|
child: IntrinsicHeight(
|
||||||
child: Stack(
|
child: Stack(
|
||||||
alignment: AlignmentDirectional.center,
|
alignment: AlignmentDirectional.topEnd,
|
||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
children: children,
|
children: children,
|
||||||
)),
|
)),
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
class BoardSizes {
|
||||||
|
static double get cardCellVPadding => 6;
|
||||||
|
}
|
@ -0,0 +1,168 @@
|
|||||||
|
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:app_flowy/plugins/board/application/toolbar/board_setting_bloc.dart';
|
||||||
|
import 'package:app_flowy/plugins/grid/application/field/field_cache.dart';
|
||||||
|
import 'package:app_flowy/plugins/grid/presentation/layout/sizes.dart';
|
||||||
|
import 'package:app_flowy/plugins/grid/presentation/widgets/toolbar/grid_property.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flowy_infra/image.dart';
|
||||||
|
import 'package:flowy_infra/theme.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/scrolling/styled_list.dart';
|
||||||
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
import 'board_toolbar.dart';
|
||||||
|
|
||||||
|
class BoardSettingContext {
|
||||||
|
final String viewId;
|
||||||
|
final GridFieldCache fieldCache;
|
||||||
|
BoardSettingContext({
|
||||||
|
required this.viewId,
|
||||||
|
required this.fieldCache,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory BoardSettingContext.from(BoardToolbarContext toolbarContext) =>
|
||||||
|
BoardSettingContext(
|
||||||
|
viewId: toolbarContext.viewId,
|
||||||
|
fieldCache: toolbarContext.fieldCache,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class BoardSettingList extends StatelessWidget {
|
||||||
|
final BoardSettingContext settingContext;
|
||||||
|
final Function(BoardSettingAction, BoardSettingContext) onAction;
|
||||||
|
const BoardSettingList({
|
||||||
|
required this.settingContext,
|
||||||
|
required this.onAction,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider(
|
||||||
|
create: (context) => BoardSettingBloc(gridId: settingContext.viewId),
|
||||||
|
child: BlocListener<BoardSettingBloc, BoardSettingState>(
|
||||||
|
listenWhen: (previous, current) =>
|
||||||
|
previous.selectedAction != current.selectedAction,
|
||||||
|
listener: (context, state) {
|
||||||
|
state.selectedAction.foldLeft(null, (_, action) {
|
||||||
|
FlowyOverlay.of(context).remove(identifier());
|
||||||
|
onAction(action, settingContext);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: BlocBuilder<BoardSettingBloc, BoardSettingState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return _renderList();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _renderList() {
|
||||||
|
final cells = BoardSettingAction.values.map((action) {
|
||||||
|
return _SettingItem(action: action);
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
return SizedBox(
|
||||||
|
width: 140,
|
||||||
|
child: ListView.separated(
|
||||||
|
shrinkWrap: true,
|
||||||
|
controller: ScrollController(),
|
||||||
|
itemCount: cells.length,
|
||||||
|
separatorBuilder: (context, index) {
|
||||||
|
return VSpace(GridSize.typeOptionSeparatorHeight);
|
||||||
|
},
|
||||||
|
physics: StyledScrollPhysics(),
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
return cells[index];
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void show(BuildContext context, BoardSettingContext settingContext) {
|
||||||
|
final list = BoardSettingList(
|
||||||
|
settingContext: settingContext,
|
||||||
|
onAction: (action, settingContext) {
|
||||||
|
switch (action) {
|
||||||
|
case BoardSettingAction.properties:
|
||||||
|
GridPropertyList(
|
||||||
|
gridId: settingContext.viewId,
|
||||||
|
fieldCache: settingContext.fieldCache)
|
||||||
|
.show(context);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
FlowyOverlay.of(context).insertWithAnchor(
|
||||||
|
widget: OverlayContainer(
|
||||||
|
constraints: BoxConstraints.loose(const Size(140, 400)),
|
||||||
|
child: list,
|
||||||
|
),
|
||||||
|
identifier: identifier(),
|
||||||
|
anchorContext: context,
|
||||||
|
anchorDirection: AnchorDirection.bottomRight,
|
||||||
|
style: FlowyOverlayStyle(blur: false),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static String identifier() {
|
||||||
|
return (BoardSettingList).toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SettingItem extends StatelessWidget {
|
||||||
|
final BoardSettingAction action;
|
||||||
|
|
||||||
|
const _SettingItem({
|
||||||
|
required this.action,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = context.read<AppTheme>();
|
||||||
|
final isSelected = context
|
||||||
|
.read<BoardSettingBloc>()
|
||||||
|
.state
|
||||||
|
.selectedAction
|
||||||
|
.foldLeft(false, (_, selectedAction) => selectedAction == action);
|
||||||
|
|
||||||
|
return SizedBox(
|
||||||
|
height: 30,
|
||||||
|
child: FlowyButton(
|
||||||
|
isSelected: isSelected,
|
||||||
|
text: FlowyText.medium(action.title(),
|
||||||
|
fontSize: 12, color: theme.textColor),
|
||||||
|
hoverColor: theme.hover,
|
||||||
|
onTap: () {
|
||||||
|
context
|
||||||
|
.read<BoardSettingBloc>()
|
||||||
|
.add(BoardSettingEvent.performAction(action));
|
||||||
|
},
|
||||||
|
leftIcon: svgWidget(action.iconName(), color: theme.iconColor),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _GridSettingExtension on BoardSettingAction {
|
||||||
|
String iconName() {
|
||||||
|
switch (this) {
|
||||||
|
case BoardSettingAction.properties:
|
||||||
|
return 'grid/setting/properties';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String title() {
|
||||||
|
switch (this) {
|
||||||
|
case BoardSettingAction.properties:
|
||||||
|
return LocaleKeys.grid_settings_Properties.tr();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
import 'package:app_flowy/plugins/grid/application/field/field_cache.dart';
|
||||||
|
import 'package:flowy_infra/image.dart';
|
||||||
|
import 'package:flowy_infra/theme.dart';
|
||||||
|
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'board_setting.dart';
|
||||||
|
|
||||||
|
class BoardToolbarContext {
|
||||||
|
final String viewId;
|
||||||
|
final GridFieldCache fieldCache;
|
||||||
|
|
||||||
|
BoardToolbarContext({
|
||||||
|
required this.viewId,
|
||||||
|
required this.fieldCache,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class BoardToolbar extends StatelessWidget {
|
||||||
|
final BoardToolbarContext toolbarContext;
|
||||||
|
const BoardToolbar({
|
||||||
|
required this.toolbarContext,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
_SettingButton(
|
||||||
|
settingContext: BoardSettingContext.from(toolbarContext),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SettingButton extends StatelessWidget {
|
||||||
|
final BoardSettingContext settingContext;
|
||||||
|
const _SettingButton({required this.settingContext, Key? key})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = context.read<AppTheme>();
|
||||||
|
return FlowyIconButton(
|
||||||
|
hoverColor: theme.hover,
|
||||||
|
width: 22,
|
||||||
|
onPressed: () => BoardSettingList.show(context, settingContext),
|
||||||
|
icon: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 3.0, horizontal: 3.0),
|
||||||
|
child: svgWidget("grid/setting/setting"),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -40,11 +40,11 @@ class DocumentBanner extends StatelessWidget {
|
|||||||
downColor: theme.main1,
|
downColor: theme.main1,
|
||||||
outlineColor: Colors.white,
|
outlineColor: Colors.white,
|
||||||
borderRadius: Corners.s8Border,
|
borderRadius: Corners.s8Border,
|
||||||
|
onPressed: onRestore,
|
||||||
child: FlowyText.medium(
|
child: FlowyText.medium(
|
||||||
LocaleKeys.deletePagePrompt_restore.tr(),
|
LocaleKeys.deletePagePrompt_restore.tr(),
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 14),
|
fontSize: 14)),
|
||||||
onPressed: onRestore),
|
|
||||||
const HSpace(20),
|
const HSpace(20),
|
||||||
BaseStyledButton(
|
BaseStyledButton(
|
||||||
minWidth: 220,
|
minWidth: 220,
|
||||||
@ -55,11 +55,11 @@ class DocumentBanner extends StatelessWidget {
|
|||||||
downColor: theme.main1,
|
downColor: theme.main1,
|
||||||
outlineColor: Colors.white,
|
outlineColor: Colors.white,
|
||||||
borderRadius: Corners.s8Border,
|
borderRadius: Corners.s8Border,
|
||||||
|
onPressed: onDelete,
|
||||||
child: FlowyText.medium(
|
child: FlowyText.medium(
|
||||||
LocaleKeys.deletePagePrompt_deletePermanent.tr(),
|
LocaleKeys.deletePagePrompt_deletePermanent.tr(),
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 14),
|
fontSize: 14)),
|
||||||
onPressed: onDelete),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -16,7 +16,10 @@ class EditorCheckboxBuilder extends QuillCheckboxBuilder {
|
|||||||
EditorCheckboxBuilder(this.theme);
|
EditorCheckboxBuilder(this.theme);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build({required BuildContext context, required bool isChecked, required ValueChanged<bool> onChanged}) {
|
Widget build(
|
||||||
|
{required BuildContext context,
|
||||||
|
required bool isChecked,
|
||||||
|
required ValueChanged<bool> onChanged}) {
|
||||||
return FlowyEditorCheckbox(
|
return FlowyEditorCheckbox(
|
||||||
theme: theme,
|
theme: theme,
|
||||||
isChecked: isChecked,
|
isChecked: isChecked,
|
||||||
@ -37,10 +40,10 @@ class FlowyEditorCheckbox extends StatefulWidget {
|
|||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_FlowyEditorCheckboxState createState() => _FlowyEditorCheckboxState();
|
FlowyEditorCheckboxState createState() => FlowyEditorCheckboxState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FlowyEditorCheckboxState extends State<FlowyEditorCheckbox> {
|
class FlowyEditorCheckboxState extends State<FlowyEditorCheckbox> {
|
||||||
late bool isChecked;
|
late bool isChecked;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -51,7 +54,9 @@ class _FlowyEditorCheckboxState extends State<FlowyEditorCheckbox> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final icon = isChecked ? svgWidget('editor/editor_check') : svgWidget('editor/editor_uncheck');
|
final icon = isChecked
|
||||||
|
? svgWidget('editor/editor_check')
|
||||||
|
: svgWidget('editor/editor_uncheck');
|
||||||
return Align(
|
return Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: FlowyIconButton(
|
child: FlowyIconButton(
|
||||||
|
@ -28,10 +28,10 @@ class FlowyCheckListButton extends StatefulWidget {
|
|||||||
final String tooltipText;
|
final String tooltipText;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_FlowyCheckListButtonState createState() => _FlowyCheckListButtonState();
|
FlowyCheckListButtonState createState() => FlowyCheckListButtonState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FlowyCheckListButtonState extends State<FlowyCheckListButton> {
|
class FlowyCheckListButtonState extends State<FlowyCheckListButton> {
|
||||||
bool? _isToggled;
|
bool? _isToggled;
|
||||||
|
|
||||||
Style get _selectionStyle => widget.controller.getSelectionStyle();
|
Style get _selectionStyle => widget.controller.getSelectionStyle();
|
||||||
|
@ -24,10 +24,10 @@ class FlowyColorButton extends StatefulWidget {
|
|||||||
final QuillIconTheme? iconTheme;
|
final QuillIconTheme? iconTheme;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_FlowyColorButtonState createState() => _FlowyColorButtonState();
|
FlowyColorButtonState createState() => FlowyColorButtonState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FlowyColorButtonState extends State<FlowyColorButton> {
|
class FlowyColorButtonState extends State<FlowyColorButton> {
|
||||||
late bool _isToggledColor;
|
late bool _isToggledColor;
|
||||||
late bool _isToggledBackground;
|
late bool _isToggledBackground;
|
||||||
late bool _isWhite;
|
late bool _isWhite;
|
||||||
@ -37,10 +37,14 @@ class _FlowyColorButtonState extends State<FlowyColorButton> {
|
|||||||
|
|
||||||
void _didChangeEditingValue() {
|
void _didChangeEditingValue() {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isToggledColor = _getIsToggledColor(widget.controller.getSelectionStyle().attributes);
|
_isToggledColor =
|
||||||
_isToggledBackground = _getIsToggledBackground(widget.controller.getSelectionStyle().attributes);
|
_getIsToggledColor(widget.controller.getSelectionStyle().attributes);
|
||||||
_isWhite = _isToggledColor && _selectionStyle.attributes['color']!.value == '#ffffff';
|
_isToggledBackground = _getIsToggledBackground(
|
||||||
_isWhitebackground = _isToggledBackground && _selectionStyle.attributes['background']!.value == '#ffffff';
|
widget.controller.getSelectionStyle().attributes);
|
||||||
|
_isWhite = _isToggledColor &&
|
||||||
|
_selectionStyle.attributes['color']!.value == '#ffffff';
|
||||||
|
_isWhitebackground = _isToggledBackground &&
|
||||||
|
_selectionStyle.attributes['background']!.value == '#ffffff';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,8 +53,10 @@ class _FlowyColorButtonState extends State<FlowyColorButton> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
_isToggledColor = _getIsToggledColor(_selectionStyle.attributes);
|
_isToggledColor = _getIsToggledColor(_selectionStyle.attributes);
|
||||||
_isToggledBackground = _getIsToggledBackground(_selectionStyle.attributes);
|
_isToggledBackground = _getIsToggledBackground(_selectionStyle.attributes);
|
||||||
_isWhite = _isToggledColor && _selectionStyle.attributes['color']!.value == '#ffffff';
|
_isWhite = _isToggledColor &&
|
||||||
_isWhitebackground = _isToggledBackground && _selectionStyle.attributes['background']!.value == '#ffffff';
|
_selectionStyle.attributes['color']!.value == '#ffffff';
|
||||||
|
_isWhitebackground = _isToggledBackground &&
|
||||||
|
_selectionStyle.attributes['background']!.value == '#ffffff';
|
||||||
widget.controller.addListener(_didChangeEditingValue);
|
widget.controller.addListener(_didChangeEditingValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,9 +75,12 @@ class _FlowyColorButtonState extends State<FlowyColorButton> {
|
|||||||
oldWidget.controller.removeListener(_didChangeEditingValue);
|
oldWidget.controller.removeListener(_didChangeEditingValue);
|
||||||
widget.controller.addListener(_didChangeEditingValue);
|
widget.controller.addListener(_didChangeEditingValue);
|
||||||
_isToggledColor = _getIsToggledColor(_selectionStyle.attributes);
|
_isToggledColor = _getIsToggledColor(_selectionStyle.attributes);
|
||||||
_isToggledBackground = _getIsToggledBackground(_selectionStyle.attributes);
|
_isToggledBackground =
|
||||||
_isWhite = _isToggledColor && _selectionStyle.attributes['color']!.value == '#ffffff';
|
_getIsToggledBackground(_selectionStyle.attributes);
|
||||||
_isWhitebackground = _isToggledBackground && _selectionStyle.attributes['background']!.value == '#ffffff';
|
_isWhite = _isToggledColor &&
|
||||||
|
_selectionStyle.attributes['color']!.value == '#ffffff';
|
||||||
|
_isWhitebackground = _isToggledBackground &&
|
||||||
|
_selectionStyle.attributes['background']!.value == '#ffffff';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,7 +97,8 @@ class _FlowyColorButtonState extends State<FlowyColorButton> {
|
|||||||
final fillColor = _isToggledColor && !widget.background && _isWhite
|
final fillColor = _isToggledColor && !widget.background && _isWhite
|
||||||
? stringToColor('#ffffff')
|
? stringToColor('#ffffff')
|
||||||
: (widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor);
|
: (widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor);
|
||||||
final fillColorBackground = _isToggledBackground && widget.background && _isWhitebackground
|
final fillColorBackground =
|
||||||
|
_isToggledBackground && widget.background && _isWhitebackground
|
||||||
? stringToColor('#ffffff')
|
? stringToColor('#ffffff')
|
||||||
: (widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor);
|
: (widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor);
|
||||||
|
|
||||||
@ -99,7 +109,8 @@ class _FlowyColorButtonState extends State<FlowyColorButton> {
|
|||||||
highlightElevation: 0,
|
highlightElevation: 0,
|
||||||
hoverElevation: 0,
|
hoverElevation: 0,
|
||||||
size: widget.iconSize * kIconButtonFactor,
|
size: widget.iconSize * kIconButtonFactor,
|
||||||
icon: Icon(widget.icon, size: widget.iconSize, color: theme.iconTheme.color),
|
icon: Icon(widget.icon,
|
||||||
|
size: widget.iconSize, color: theme.iconTheme.color),
|
||||||
fillColor: widget.background ? fillColorBackground : fillColor,
|
fillColor: widget.background ? fillColorBackground : fillColor,
|
||||||
onPressed: _showColorPicker,
|
onPressed: _showColorPicker,
|
||||||
),
|
),
|
||||||
@ -112,13 +123,16 @@ class _FlowyColorButtonState extends State<FlowyColorButton> {
|
|||||||
hex = hex.substring(2);
|
hex = hex.substring(2);
|
||||||
}
|
}
|
||||||
hex = '#$hex';
|
hex = '#$hex';
|
||||||
widget.controller.formatSelection(widget.background ? BackgroundAttribute(hex) : ColorAttribute(hex));
|
widget.controller.formatSelection(
|
||||||
|
widget.background ? BackgroundAttribute(hex) : ColorAttribute(hex));
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showColorPicker() {
|
void _showColorPicker() {
|
||||||
final style = widget.controller.getSelectionStyle();
|
final style = widget.controller.getSelectionStyle();
|
||||||
final values = style.values.where((v) => v.key == Attribute.background.key).map((v) => v.value);
|
final values = style.values
|
||||||
|
.where((v) => v.key == Attribute.background.key)
|
||||||
|
.map((v) => v.value);
|
||||||
int initialColor = 0;
|
int initialColor = 0;
|
||||||
if (values.isNotEmpty) {
|
if (values.isNotEmpty) {
|
||||||
assert(values.length == 1);
|
assert(values.length == 1);
|
||||||
@ -160,7 +174,9 @@ class FlowyColorPicker extends StatefulWidget {
|
|||||||
];
|
];
|
||||||
final Function(Color?) onColorChanged;
|
final Function(Color?) onColorChanged;
|
||||||
final int initialColor;
|
final int initialColor;
|
||||||
FlowyColorPicker({Key? key, required this.onColorChanged, this.initialColor = 0}) : super(key: key);
|
FlowyColorPicker(
|
||||||
|
{Key? key, required this.onColorChanged, this.initialColor = 0})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<FlowyColorPicker> createState() => _FlowyColorPickerState();
|
State<FlowyColorPicker> createState() => _FlowyColorPickerState();
|
||||||
@ -178,8 +194,10 @@ class _FlowyColorPickerState extends State<FlowyColorPicker> {
|
|||||||
const double crossAxisSpacing = 10;
|
const double crossAxisSpacing = 10;
|
||||||
final numberOfRows = (widget.colors.length / crossAxisCount).ceil();
|
final numberOfRows = (widget.colors.length / crossAxisCount).ceil();
|
||||||
|
|
||||||
const perRowHeight = ((width - ((crossAxisCount - 1) * mainAxisSpacing)) / crossAxisCount);
|
const perRowHeight =
|
||||||
final totalHeight = numberOfRows * perRowHeight + numberOfRows * crossAxisSpacing;
|
((width - ((crossAxisCount - 1) * mainAxisSpacing)) / crossAxisCount);
|
||||||
|
final totalHeight =
|
||||||
|
numberOfRows * perRowHeight + numberOfRows * crossAxisSpacing;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
constraints: BoxConstraints.tightFor(width: width, height: totalHeight),
|
constraints: BoxConstraints.tightFor(width: width, height: totalHeight),
|
||||||
@ -198,7 +216,8 @@ class _FlowyColorPickerState extends State<FlowyColorPicker> {
|
|||||||
delegate: SliverChildBuilderDelegate(
|
delegate: SliverChildBuilderDelegate(
|
||||||
(BuildContext context, int index) {
|
(BuildContext context, int index) {
|
||||||
if (widget.colors.length > index) {
|
if (widget.colors.length > index) {
|
||||||
final isSelected = widget.colors[index] == widget.initialColor;
|
final isSelected =
|
||||||
|
widget.colors[index] == widget.initialColor;
|
||||||
return ColorItem(
|
return ColorItem(
|
||||||
color: Color(widget.colors[index]),
|
color: Color(widget.colors[index]),
|
||||||
onPressed: widget.onColorChanged,
|
onPressed: widget.onColorChanged,
|
||||||
@ -242,7 +261,8 @@ class ColorItem extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return RawMaterialButton(
|
return RawMaterialButton(
|
||||||
shape: const CircleBorder(side: BorderSide(color: Colors.white, width: 8)) +
|
shape: const CircleBorder(
|
||||||
|
side: BorderSide(color: Colors.white, width: 8)) +
|
||||||
CircleBorder(side: BorderSide(color: color, width: 4)),
|
CircleBorder(side: BorderSide(color: color, width: 4)),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
|
@ -16,10 +16,10 @@ class FlowyHeaderStyleButton extends StatefulWidget {
|
|||||||
final double iconSize;
|
final double iconSize;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_FlowyHeaderStyleButtonState createState() => _FlowyHeaderStyleButtonState();
|
FlowyHeaderStyleButtonState createState() => FlowyHeaderStyleButtonState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FlowyHeaderStyleButtonState extends State<FlowyHeaderStyleButton> {
|
class FlowyHeaderStyleButtonState extends State<FlowyHeaderStyleButton> {
|
||||||
Attribute? _value;
|
Attribute? _value;
|
||||||
|
|
||||||
Style get _selectionStyle => widget.controller.getSelectionStyle();
|
Style get _selectionStyle => widget.controller.getSelectionStyle();
|
||||||
@ -28,22 +28,27 @@ class _FlowyHeaderStyleButtonState extends State<FlowyHeaderStyleButton> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
setState(() {
|
setState(() {
|
||||||
_value = _selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
|
_value =
|
||||||
|
_selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
|
||||||
});
|
});
|
||||||
widget.controller.addListener(_didChangeEditingValue);
|
widget.controller.addListener(_didChangeEditingValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final _valueToText = <Attribute, String>{
|
final valueToText = <Attribute, String>{
|
||||||
Attribute.h1: 'H1',
|
Attribute.h1: 'H1',
|
||||||
Attribute.h2: 'H2',
|
Attribute.h2: 'H2',
|
||||||
Attribute.h3: 'H3',
|
Attribute.h3: 'H3',
|
||||||
};
|
};
|
||||||
|
|
||||||
final _valueAttribute = <Attribute>[Attribute.h1, Attribute.h2, Attribute.h3];
|
final valueAttribute = <Attribute>[
|
||||||
final _valueString = <String>['H1', 'H2', 'H3'];
|
Attribute.h1,
|
||||||
final _attributeImageName = <String>['editor/H1', 'editor/H2', 'editor/H3'];
|
Attribute.h2,
|
||||||
|
Attribute.h3
|
||||||
|
];
|
||||||
|
final valueString = <String>['H1', 'H2', 'H3'];
|
||||||
|
final attributeImageName = <String>['editor/H1', 'editor/H2', 'editor/H3'];
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@ -52,18 +57,18 @@ class _FlowyHeaderStyleButtonState extends State<FlowyHeaderStyleButton> {
|
|||||||
// _valueToText[_value] == _valueString[index] ? svg('editor/H1', color: Colors.white) : svg('editor/H1');
|
// _valueToText[_value] == _valueString[index] ? svg('editor/H1', color: Colors.white) : svg('editor/H1');
|
||||||
|
|
||||||
final headerTitle = "${LocaleKeys.toolbar_header.tr()} ${index + 1}";
|
final headerTitle = "${LocaleKeys.toolbar_header.tr()} ${index + 1}";
|
||||||
final _isToggled = _valueToText[_value] == _valueString[index];
|
final isToggled = valueToText[_value] == valueString[index];
|
||||||
return ToolbarIconButton(
|
return ToolbarIconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (_isToggled) {
|
if (isToggled) {
|
||||||
widget.controller.formatSelection(Attribute.header);
|
widget.controller.formatSelection(Attribute.header);
|
||||||
} else {
|
} else {
|
||||||
widget.controller.formatSelection(_valueAttribute[index]);
|
widget.controller.formatSelection(valueAttribute[index]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
width: widget.iconSize * kIconButtonFactor,
|
width: widget.iconSize * kIconButtonFactor,
|
||||||
iconName: _attributeImageName[index],
|
iconName: attributeImageName[index],
|
||||||
isToggled: _isToggled,
|
isToggled: isToggled,
|
||||||
tooltipText: headerTitle,
|
tooltipText: headerTitle,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@ -72,7 +77,8 @@ class _FlowyHeaderStyleButtonState extends State<FlowyHeaderStyleButton> {
|
|||||||
|
|
||||||
void _didChangeEditingValue() {
|
void _didChangeEditingValue() {
|
||||||
setState(() {
|
setState(() {
|
||||||
_value = _selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
|
_value =
|
||||||
|
_selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +88,8 @@ class _FlowyHeaderStyleButtonState extends State<FlowyHeaderStyleButton> {
|
|||||||
if (oldWidget.controller != widget.controller) {
|
if (oldWidget.controller != widget.controller) {
|
||||||
oldWidget.controller.removeListener(_didChangeEditingValue);
|
oldWidget.controller.removeListener(_didChangeEditingValue);
|
||||||
widget.controller.addListener(_didChangeEditingValue);
|
widget.controller.addListener(_didChangeEditingValue);
|
||||||
_value = _selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
|
_value =
|
||||||
|
_selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,10 +19,10 @@ class FlowyLinkStyleButton extends StatefulWidget {
|
|||||||
final double iconSize;
|
final double iconSize;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_FlowyLinkStyleButtonState createState() => _FlowyLinkStyleButtonState();
|
FlowyLinkStyleButtonState createState() => FlowyLinkStyleButtonState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FlowyLinkStyleButtonState extends State<FlowyLinkStyleButton> {
|
class FlowyLinkStyleButtonState extends State<FlowyLinkStyleButton> {
|
||||||
void _didChangeSelection() {
|
void _didChangeSelection() {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
@ -75,7 +75,9 @@ class _FlowyLinkStyleButtonState extends State<FlowyLinkStyleButton> {
|
|||||||
|
|
||||||
void _openLinkDialog(BuildContext context) {
|
void _openLinkDialog(BuildContext context) {
|
||||||
final style = widget.controller.getSelectionStyle();
|
final style = widget.controller.getSelectionStyle();
|
||||||
final values = style.values.where((v) => v.key == Attribute.link.key).map((v) => v.value);
|
final values = style.values
|
||||||
|
.where((v) => v.key == Attribute.link.key)
|
||||||
|
.map((v) => v.value);
|
||||||
String value = "";
|
String value = "";
|
||||||
if (values.isNotEmpty) {
|
if (values.isNotEmpty) {
|
||||||
assert(values.length == 1);
|
assert(values.length == 1);
|
||||||
|
@ -21,10 +21,10 @@ class FlowyToggleStyleButton extends StatefulWidget {
|
|||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_ToggleStyleButtonState createState() => _ToggleStyleButtonState();
|
ToggleStyleButtonState createState() => ToggleStyleButtonState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ToggleStyleButtonState extends State<FlowyToggleStyleButton> {
|
class ToggleStyleButtonState extends State<FlowyToggleStyleButton> {
|
||||||
bool? _isToggled;
|
bool? _isToggled;
|
||||||
Style get _selectionStyle => widget.controller.getSelectionStyle();
|
Style get _selectionStyle => widget.controller.getSelectionStyle();
|
||||||
@override
|
@override
|
||||||
@ -77,6 +77,8 @@ class _ToggleStyleButtonState extends State<FlowyToggleStyleButton> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _toggleAttribute() {
|
void _toggleAttribute() {
|
||||||
widget.controller.formatSelection(_isToggled! ? Attribute.clone(widget.attribute, null) : widget.attribute);
|
widget.controller.formatSelection(_isToggled!
|
||||||
|
? Attribute.clone(widget.attribute, null)
|
||||||
|
: widget.attribute);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,8 @@ class EditorToolbar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
return Container(
|
return Container(
|
||||||
color: Theme.of(context).canvasColor,
|
color: Theme.of(context).canvasColor,
|
||||||
constraints: BoxConstraints.tightFor(height: preferredSize.height),
|
constraints: BoxConstraints.tightFor(height: preferredSize.height),
|
||||||
child: ToolbarButtonList(buttons: children).padding(horizontal: 4, vertical: 4),
|
child: ToolbarButtonList(buttons: children)
|
||||||
|
.padding(horizontal: 4, vertical: 4),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,10 +169,11 @@ class ToolbarButtonList extends StatefulWidget {
|
|||||||
final List<Widget> buttons;
|
final List<Widget> buttons;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_ToolbarButtonListState createState() => _ToolbarButtonListState();
|
ToolbarButtonListState createState() => ToolbarButtonListState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ToolbarButtonListState extends State<ToolbarButtonList> with WidgetsBindingObserver {
|
class ToolbarButtonListState extends State<ToolbarButtonList>
|
||||||
|
with WidgetsBindingObserver {
|
||||||
final ScrollController _controller = ScrollController();
|
final ScrollController _controller = ScrollController();
|
||||||
bool _showLeftArrow = false;
|
bool _showLeftArrow = false;
|
||||||
bool _showRightArrow = false;
|
bool _showRightArrow = false;
|
||||||
@ -196,7 +198,8 @@ class _ToolbarButtonListState extends State<ToolbarButtonList> with WidgetsBindi
|
|||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
builder: (BuildContext context, BoxConstraints constraints) {
|
builder: (BuildContext context, BoxConstraints constraints) {
|
||||||
List<Widget> children = [];
|
List<Widget> children = [];
|
||||||
double width = (widget.buttons.length + 2) * defaultIconSize * kIconButtonFactor;
|
double width =
|
||||||
|
(widget.buttons.length + 2) * defaultIconSize * kIconButtonFactor;
|
||||||
final isFit = constraints.maxWidth > width;
|
final isFit = constraints.maxWidth > width;
|
||||||
if (!isFit) {
|
if (!isFit) {
|
||||||
children.add(_buildLeftArrow());
|
children.add(_buildLeftArrow());
|
||||||
@ -233,8 +236,10 @@ class _ToolbarButtonListState extends State<ToolbarButtonList> with WidgetsBindi
|
|||||||
void _handleScroll() {
|
void _handleScroll() {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
_showLeftArrow = _controller.position.minScrollExtent != _controller.position.pixels;
|
_showLeftArrow =
|
||||||
_showRightArrow = _controller.position.maxScrollExtent != _controller.position.pixels;
|
_controller.position.minScrollExtent != _controller.position.pixels;
|
||||||
|
_showRightArrow =
|
||||||
|
_controller.position.maxScrollExtent != _controller.position.pixels;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,13 +11,15 @@ typedef UpdateFieldNotifiedValue = Either<Unit, FlowyError>;
|
|||||||
class CellListener {
|
class CellListener {
|
||||||
final String rowId;
|
final String rowId;
|
||||||
final String fieldId;
|
final String fieldId;
|
||||||
PublishNotifier<UpdateFieldNotifiedValue>? _updateCellNotifier = PublishNotifier();
|
PublishNotifier<UpdateFieldNotifiedValue>? _updateCellNotifier =
|
||||||
|
PublishNotifier();
|
||||||
GridNotificationListener? _listener;
|
GridNotificationListener? _listener;
|
||||||
CellListener({required this.rowId, required this.fieldId});
|
CellListener({required this.rowId, required this.fieldId});
|
||||||
|
|
||||||
void start({required void Function(UpdateFieldNotifiedValue) onCellChanged}) {
|
void start({required void Function(UpdateFieldNotifiedValue) onCellChanged}) {
|
||||||
_updateCellNotifier?.addPublishListener(onCellChanged);
|
_updateCellNotifier?.addPublishListener(onCellChanged);
|
||||||
_listener = GridNotificationListener(objectId: "$rowId:$fieldId", handler: _handler);
|
_listener = GridNotificationListener(
|
||||||
|
objectId: "$rowId:$fieldId", handler: _handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handler(GridNotification ty, Either<Uint8List, FlowyError> result) {
|
void _handler(GridNotification ty, Either<Uint8List, FlowyError> result) {
|
||||||
|
@ -33,10 +33,17 @@ class GridCellCache {
|
|||||||
required this.gridId,
|
required this.gridId,
|
||||||
});
|
});
|
||||||
|
|
||||||
void remove(String fieldId) {
|
void removeCellWithFieldId(String fieldId) {
|
||||||
_cellDataByFieldId.remove(fieldId);
|
_cellDataByFieldId.remove(fieldId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void remove(GridCellCacheKey key) {
|
||||||
|
var map = _cellDataByFieldId[key.fieldId];
|
||||||
|
if (map != null) {
|
||||||
|
map.remove(key.rowId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void insert<T extends GridCell>(GridCellCacheKey key, T value) {
|
void insert<T extends GridCell>(GridCellCacheKey key, T value) {
|
||||||
var map = _cellDataByFieldId[key.fieldId];
|
var map = _cellDataByFieldId[key.fieldId];
|
||||||
if (map == null) {
|
if (map == null) {
|
||||||
|
@ -24,7 +24,8 @@ class GridCellDataLoader<T> {
|
|||||||
Future<T?> loadData() {
|
Future<T?> loadData() {
|
||||||
final fut = service.getCell(cellId: cellId);
|
final fut = service.getCell(cellId: cellId);
|
||||||
return fut.then(
|
return fut.then(
|
||||||
(result) => result.fold((GridCellPB cell) {
|
(result) => result.fold(
|
||||||
|
(GridCellPB cell) {
|
||||||
try {
|
try {
|
||||||
return parser.parserData(cell.data);
|
return parser.parserData(cell.data);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
@ -32,10 +33,12 @@ class GridCellDataLoader<T> {
|
|||||||
Log.error('Stack trace \n $s');
|
Log.error('Stack trace \n $s');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}, (err) {
|
},
|
||||||
|
(err) {
|
||||||
Log.error(err);
|
Log.error(err);
|
||||||
return null;
|
return null;
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -58,7 +61,8 @@ class DateCellDataParser implements IGridCellDataParser<DateCellDataPB> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SelectOptionCellDataParser implements IGridCellDataParser<SelectOptionCellDataPB> {
|
class SelectOptionCellDataParser
|
||||||
|
implements IGridCellDataParser<SelectOptionCellDataPB> {
|
||||||
@override
|
@override
|
||||||
SelectOptionCellDataPB? parserData(List<int> data) {
|
SelectOptionCellDataPB? parserData(List<int> data) {
|
||||||
if (data.isEmpty) {
|
if (data.isEmpty) {
|
||||||
|
@ -71,6 +71,6 @@ class GridCellIdentifier with _$GridCellIdentifier {
|
|||||||
FieldType get fieldType => field.fieldType;
|
FieldType get fieldType => field.fieldType;
|
||||||
|
|
||||||
ValueKey key() {
|
ValueKey key() {
|
||||||
return ValueKey(rowId + fieldId + "${field.fieldType}");
|
return ValueKey("$rowId$fieldId${field.fieldType}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -190,7 +190,10 @@ class IGridCellController<T, D> extends Equatable {
|
|||||||
/// cell display: $12
|
/// cell display: $12
|
||||||
_cellListener?.start(onCellChanged: (result) {
|
_cellListener?.start(onCellChanged: (result) {
|
||||||
result.fold(
|
result.fold(
|
||||||
(_) => _loadData(),
|
(_) {
|
||||||
|
_cellsCache.remove(_cacheKey);
|
||||||
|
_loadData();
|
||||||
|
},
|
||||||
(err) => Log.error(err),
|
(err) => Log.error(err),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -279,8 +282,8 @@ class IGridCellController<T, D> extends Equatable {
|
|||||||
_loadDataOperation?.cancel();
|
_loadDataOperation?.cancel();
|
||||||
_loadDataOperation = Timer(const Duration(milliseconds: 10), () {
|
_loadDataOperation = Timer(const Duration(milliseconds: 10), () {
|
||||||
_cellDataLoader.loadData().then((data) {
|
_cellDataLoader.loadData().then((data) {
|
||||||
_cellDataNotifier?.value = data;
|
|
||||||
_cellsCache.insert(_cacheKey, GridCell(object: data));
|
_cellsCache.insert(_cacheKey, GridCell(object: data));
|
||||||
|
_cellDataNotifier?.value = data;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -119,13 +119,13 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String timeFormatPrompt(FlowyError error) {
|
String timeFormatPrompt(FlowyError error) {
|
||||||
String msg = LocaleKeys.grid_field_invalidTimeFormat.tr() + ". ";
|
String msg = "${LocaleKeys.grid_field_invalidTimeFormat.tr()}. ";
|
||||||
switch (state.dateTypeOptionPB.timeFormat) {
|
switch (state.dateTypeOptionPB.timeFormat) {
|
||||||
case TimeFormat.TwelveHour:
|
case TimeFormat.TwelveHour:
|
||||||
msg = msg + "e.g. 01: 00 AM";
|
msg = "${msg}e.g. 01: 00 AM";
|
||||||
break;
|
break;
|
||||||
case TimeFormat.TwentyFourHour:
|
case TimeFormat.TwentyFourHour:
|
||||||
msg = msg + "e.g. 13: 00";
|
msg = "${msg}e.g. 13: 00";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -79,7 +79,7 @@ class DateCellState with _$DateCellState {
|
|||||||
String _dateStrFromCellData(DateCellDataPB? cellData) {
|
String _dateStrFromCellData(DateCellDataPB? cellData) {
|
||||||
String dateStr = "";
|
String dateStr = "";
|
||||||
if (cellData != null) {
|
if (cellData != null) {
|
||||||
dateStr = cellData.date + " " + cellData.time;
|
dateStr = "${cellData.date} ${cellData.time}";
|
||||||
}
|
}
|
||||||
return dateStr;
|
return dateStr;
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||||
|
import 'package:collection/collection.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/select_option.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
|
||||||
import 'select_option_service.dart';
|
import 'select_option_service.dart';
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
|
|
||||||
part 'select_option_editor_bloc.freezed.dart';
|
part 'select_option_editor_bloc.freezed.dart';
|
||||||
|
|
||||||
|
@ -46,7 +46,8 @@ class GridDataController {
|
|||||||
|
|
||||||
GridDataController({required ViewPB view})
|
GridDataController({required ViewPB view})
|
||||||
: gridId = view.id,
|
: gridId = view.id,
|
||||||
_blocks = LinkedHashMap.new(),
|
// ignore: prefer_collection_literals
|
||||||
|
_blocks = LinkedHashMap(),
|
||||||
_gridFFIService = GridFFIService(gridId: view.id),
|
_gridFFIService = GridFFIService(gridId: view.id),
|
||||||
fieldCache = GridFieldCache(gridId: view.id);
|
fieldCache = GridFieldCache(gridId: view.id);
|
||||||
|
|
||||||
|
@ -27,10 +27,18 @@ class GridFFIService {
|
|||||||
return GridEventCreateTableRow(payload).send();
|
return GridEventCreateTableRow(payload).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Either<RowPB, FlowyError>> createBoardCard(String groupId) {
|
Future<Either<RowPB, FlowyError>> createBoardCard(
|
||||||
|
String groupId,
|
||||||
|
String? startRowId,
|
||||||
|
) {
|
||||||
CreateBoardCardPayloadPB payload = CreateBoardCardPayloadPB.create()
|
CreateBoardCardPayloadPB payload = CreateBoardCardPayloadPB.create()
|
||||||
..gridId = gridId
|
..gridId = gridId
|
||||||
..groupId = groupId;
|
..groupId = groupId;
|
||||||
|
|
||||||
|
if (startRowId != null) {
|
||||||
|
payload.startRowId = startRowId;
|
||||||
|
}
|
||||||
|
|
||||||
return GridEventCreateBoardCard(payload).send();
|
return GridEventCreateBoardCard(payload).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +52,8 @@ class GridRowCache {
|
|||||||
//
|
//
|
||||||
notifier.onRowFieldsChanged(() => _rowChangeReasonNotifier
|
notifier.onRowFieldsChanged(() => _rowChangeReasonNotifier
|
||||||
.receive(const RowsChangedReason.fieldDidChange()));
|
.receive(const RowsChangedReason.fieldDidChange()));
|
||||||
notifier.onRowFieldChanged((field) => _cellCache.remove(field.id));
|
notifier.onRowFieldChanged(
|
||||||
|
(field) => _cellCache.removeCellWithFieldId(field.id));
|
||||||
_rowInfos = block.rows.map((rowPB) => buildGridRow(rowPB)).toList();
|
_rowInfos = block.rows.map((rowPB) => buildGridRow(rowPB)).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,7 +210,8 @@ class GridRowCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
GridCellMap _makeGridCells(String rowId, RowPB? row) {
|
GridCellMap _makeGridCells(String rowId, RowPB? row) {
|
||||||
var cellDataMap = GridCellMap.new();
|
// ignore: prefer_collection_literals
|
||||||
|
var cellDataMap = GridCellMap();
|
||||||
for (final field in _fieldNotifier.fields) {
|
for (final field in _fieldNotifier.fields) {
|
||||||
if (field.visibility) {
|
if (field.visibility) {
|
||||||
cellDataMap[field.id] = GridCellIdentifier(
|
cellDataMap[field.id] = GridCellIdentifier(
|
||||||
|
@ -190,12 +190,12 @@ class CellAccessoryContainer extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
child: hover,
|
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onTap: () => accessory.onTap(),
|
onTap: () => accessory.onTap(),
|
||||||
|
child: hover,
|
||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
return Wrap(children: children, spacing: 6);
|
return Wrap(spacing: 6, children: children);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,8 +44,8 @@ class CellContainer extends StatelessWidget {
|
|||||||
|
|
||||||
if (accessories.isNotEmpty) {
|
if (accessories.isNotEmpty) {
|
||||||
container = _GridCellEnterRegion(
|
container = _GridCellEnterRegion(
|
||||||
child: container,
|
|
||||||
accessories: accessories,
|
accessories: accessories,
|
||||||
|
child: container,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -297,9 +297,8 @@ class _DateTypeOptionButton extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = context.watch<AppTheme>();
|
final theme = context.watch<AppTheme>();
|
||||||
final title = LocaleKeys.grid_field_dateFormat.tr() +
|
final title =
|
||||||
" &" +
|
"${LocaleKeys.grid_field_dateFormat.tr()} &${LocaleKeys.grid_field_timeFormat.tr()}";
|
||||||
LocaleKeys.grid_field_timeFormat.tr();
|
|
||||||
return BlocSelector<DateCalBloc, DateCalState, DateTypeOptionPB>(
|
return BlocSelector<DateCalBloc, DateCalState, DateTypeOptionPB>(
|
||||||
selector: (state) => state.dateTypeOptionPB,
|
selector: (state) => state.dateTypeOptionPB,
|
||||||
builder: (context, dateTypeOptionPB) {
|
builder: (context, dateTypeOptionPB) {
|
||||||
@ -406,8 +405,8 @@ class _CalDateTimeSettingState extends State<_CalDateTimeSetting> {
|
|||||||
overlayIdentifier = child.toString();
|
overlayIdentifier = child.toString();
|
||||||
FlowyOverlay.of(context).insertWithAnchor(
|
FlowyOverlay.of(context).insertWithAnchor(
|
||||||
widget: OverlayContainer(
|
widget: OverlayContainer(
|
||||||
child: child,
|
|
||||||
constraints: BoxConstraints.loose(const Size(460, 440)),
|
constraints: BoxConstraints.loose(const Size(460, 440)),
|
||||||
|
child: child,
|
||||||
),
|
),
|
||||||
identifier: overlayIdentifier!,
|
identifier: overlayIdentifier!,
|
||||||
anchorContext: context,
|
anchorContext: context,
|
||||||
|
@ -91,8 +91,11 @@ class SelectOptionTag extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ChoiceChip(
|
return ChoiceChip(
|
||||||
pressElevation: 1,
|
pressElevation: 1,
|
||||||
label:
|
label: FlowyText.medium(
|
||||||
FlowyText.medium(name, fontSize: 12, overflow: TextOverflow.ellipsis),
|
name,
|
||||||
|
fontSize: 12,
|
||||||
|
overflow: TextOverflow.clip,
|
||||||
|
),
|
||||||
selectedColor: color,
|
selectedColor: color,
|
||||||
backgroundColor: color,
|
backgroundColor: color,
|
||||||
labelPadding: const EdgeInsets.symmetric(horizontal: 6),
|
labelPadding: const EdgeInsets.symmetric(horizontal: 6),
|
||||||
|
@ -178,14 +178,14 @@ class _SelectOptionWrapState extends State<SelectOptionWrap> {
|
|||||||
child = Align(
|
child = Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: Wrap(
|
child: Wrap(
|
||||||
|
spacing: 4,
|
||||||
|
runSpacing: 2,
|
||||||
children: widget.selectOptions
|
children: widget.selectOptions
|
||||||
.map((option) => SelectOptionTag.fromOption(
|
.map((option) => SelectOptionTag.fromOption(
|
||||||
context: context,
|
context: context,
|
||||||
option: option,
|
option: option,
|
||||||
))
|
))
|
||||||
.toList(),
|
.toList(),
|
||||||
spacing: 4,
|
|
||||||
runSpacing: 2,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -75,8 +75,8 @@ class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
|
|||||||
//
|
//
|
||||||
FlowyOverlay.of(context).insertWithAnchor(
|
FlowyOverlay.of(context).insertWithAnchor(
|
||||||
widget: OverlayContainer(
|
widget: OverlayContainer(
|
||||||
child: SizedBox(width: _editorPannelWidth, child: editor),
|
|
||||||
constraints: BoxConstraints.loose(const Size(_editorPannelWidth, 300)),
|
constraints: BoxConstraints.loose(const Size(_editorPannelWidth, 300)),
|
||||||
|
child: SizedBox(width: _editorPannelWidth, child: editor),
|
||||||
),
|
),
|
||||||
identifier: SelectOptionCellEditor.identifier(),
|
identifier: SelectOptionCellEditor.identifier(),
|
||||||
anchorContext: context,
|
anchorContext: context,
|
||||||
|
@ -108,7 +108,7 @@ class SelectOptionTextField extends StatelessWidget {
|
|||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
controller: sc,
|
controller: sc,
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
child: Wrap(children: children, spacing: 4),
|
child: Wrap(spacing: 4, children: children),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -30,11 +30,11 @@ class URLCellEditor extends StatefulWidget with FlowyOverlayDelegate {
|
|||||||
//
|
//
|
||||||
FlowyOverlay.of(context).insertWithAnchor(
|
FlowyOverlay.of(context).insertWithAnchor(
|
||||||
widget: OverlayContainer(
|
widget: OverlayContainer(
|
||||||
|
constraints: BoxConstraints.loose(const Size(300, 160)),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 200,
|
width: 200,
|
||||||
child: Padding(padding: const EdgeInsets.all(6), child: editor),
|
child: Padding(padding: const EdgeInsets.all(6), child: editor),
|
||||||
),
|
),
|
||||||
constraints: BoxConstraints.loose(const Size(300, 160)),
|
|
||||||
),
|
),
|
||||||
identifier: URLCellEditor.identifier(),
|
identifier: URLCellEditor.identifier(),
|
||||||
anchorContext: context,
|
anchorContext: context,
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
|
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||||
import 'package:app_flowy/plugins/grid/application/field/field_cache.dart';
|
import 'package:app_flowy/plugins/grid/application/field/field_cache.dart';
|
||||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
|
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
|
||||||
import 'package:app_flowy/startup/startup.dart';
|
import 'package:app_flowy/startup/startup.dart';
|
||||||
import 'package:app_flowy/plugins/grid/application/prelude.dart';
|
import 'package:app_flowy/plugins/grid/application/prelude.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:appflowy_popover/popover.dart';
|
import 'package:appflowy_popover/popover.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';
|
||||||
@ -178,7 +180,10 @@ class CreateFieldButton extends StatelessWidget {
|
|||||||
triggerActions: PopoverTriggerActionFlags.click,
|
triggerActions: PopoverTriggerActionFlags.click,
|
||||||
direction: PopoverDirection.bottomWithRightAligned,
|
direction: PopoverDirection.bottomWithRightAligned,
|
||||||
child: FlowyButton(
|
child: FlowyButton(
|
||||||
text: const FlowyText.medium('New column', fontSize: 12),
|
text: FlowyText.medium(
|
||||||
|
LocaleKeys.grid_field_newColumn.tr(),
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
hoverColor: theme.shader6,
|
hoverColor: theme.shader6,
|
||||||
onTap: () {},
|
onTap: () {},
|
||||||
leftIcon: svgWidget("home/add"),
|
leftIcon: svgWidget("home/add"),
|
||||||
|
@ -101,10 +101,10 @@ class NumberTypeOptionWidget extends TypeOptionWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef _SelectNumberFormatCallback = Function(NumberFormat format);
|
typedef SelectNumberFormatCallback = Function(NumberFormat format);
|
||||||
|
|
||||||
class NumberFormatList extends StatelessWidget {
|
class NumberFormatList extends StatelessWidget {
|
||||||
final _SelectNumberFormatCallback onSelected;
|
final SelectNumberFormatCallback onSelected;
|
||||||
final NumberFormat selectedFormat;
|
final NumberFormat selectedFormat;
|
||||||
const NumberFormatList(
|
const NumberFormatList(
|
||||||
{required this.selectedFormat, required this.onSelected, Key? key})
|
{required this.selectedFormat, required this.onSelected, Key? key})
|
||||||
|
@ -14,6 +14,8 @@ import '../cell/cell_accessory.dart';
|
|||||||
import '../cell/cell_container.dart';
|
import '../cell/cell_container.dart';
|
||||||
import '../cell/prelude.dart';
|
import '../cell/prelude.dart';
|
||||||
import 'row_action_sheet.dart';
|
import 'row_action_sheet.dart';
|
||||||
|
import "package:app_flowy/generated/locale_keys.g.dart";
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
class GridRowWidget extends StatefulWidget {
|
class GridRowWidget extends StatefulWidget {
|
||||||
final RowInfo rowInfo;
|
final RowInfo rowInfo;
|
||||||
@ -122,10 +124,13 @@ class _InsertRowButton extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = context.watch<AppTheme>();
|
final theme = context.watch<AppTheme>();
|
||||||
return FlowyIconButton(
|
return FlowyIconButton(
|
||||||
|
tooltipText: LocaleKeys.tooltip_addNewRow.tr(),
|
||||||
hoverColor: theme.hover,
|
hoverColor: theme.hover,
|
||||||
width: 20,
|
width: 20,
|
||||||
height: 30,
|
height: 30,
|
||||||
onPressed: () => context.read<RowBloc>().add(const RowEvent.createRow()),
|
onPressed: () => context.read<RowBloc>().add(
|
||||||
|
const RowEvent.createRow(),
|
||||||
|
),
|
||||||
iconPadding: const EdgeInsets.all(3),
|
iconPadding: const EdgeInsets.all(3),
|
||||||
icon: svgWidget("home/add"),
|
icon: svgWidget("home/add"),
|
||||||
);
|
);
|
||||||
@ -139,6 +144,7 @@ class _DeleteRowButton extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = context.watch<AppTheme>();
|
final theme = context.watch<AppTheme>();
|
||||||
return FlowyIconButton(
|
return FlowyIconButton(
|
||||||
|
tooltipText: LocaleKeys.tooltip_openMenu.tr(),
|
||||||
hoverColor: theme.hover,
|
hoverColor: theme.hover,
|
||||||
width: 20,
|
width: 20,
|
||||||
height: 30,
|
height: 30,
|
||||||
@ -184,7 +190,6 @@ class RowContent extends StatelessWidget {
|
|||||||
|
|
||||||
return CellContainer(
|
return CellContainer(
|
||||||
width: cellId.field.width.toDouble(),
|
width: cellId.field.width.toDouble(),
|
||||||
child: child,
|
|
||||||
rowStateNotifier:
|
rowStateNotifier:
|
||||||
Provider.of<RegionStateNotifier>(context, listen: false),
|
Provider.of<RegionStateNotifier>(context, listen: false),
|
||||||
accessoryBuilder: (buildContext) {
|
accessoryBuilder: (buildContext) {
|
||||||
@ -202,6 +207,7 @@ class RowContent extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
return accessories;
|
return accessories;
|
||||||
},
|
},
|
||||||
|
child: child,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
).toList();
|
).toList();
|
||||||
|
@ -59,8 +59,8 @@ class GridRowActionSheet extends StatelessWidget {
|
|||||||
}) {
|
}) {
|
||||||
FlowyOverlay.of(overlayContext).insertWithAnchor(
|
FlowyOverlay.of(overlayContext).insertWithAnchor(
|
||||||
widget: OverlayContainer(
|
widget: OverlayContainer(
|
||||||
child: this,
|
|
||||||
constraints: BoxConstraints.loose(const Size(140, 200)),
|
constraints: BoxConstraints.loose(const Size(140, 200)),
|
||||||
|
child: this,
|
||||||
),
|
),
|
||||||
identifier: GridRowActionSheet.identifier(),
|
identifier: GridRowActionSheet.identifier(),
|
||||||
anchorContext: overlayContext,
|
anchorContext: overlayContext,
|
||||||
|
@ -5,8 +5,10 @@ import 'package:app_flowy/plugins/grid/application/row/row_detail_bloc.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/button.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_scroll_bar.dart';
|
import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.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:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||||
@ -61,7 +63,12 @@ class _RowDetailPageState extends State<RowDetailPage> {
|
|||||||
children: const [Spacer(), _CloseButton()],
|
children: const [Spacer(), _CloseButton()],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(child: _PropertyList(cellBuilder: widget.cellBuilder)),
|
Expanded(
|
||||||
|
child: _PropertyList(
|
||||||
|
cellBuilder: widget.cellBuilder,
|
||||||
|
viewId: widget.dataController.rowInfo.gridId,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -88,9 +95,11 @@ class _CloseButton extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _PropertyList extends StatelessWidget {
|
class _PropertyList extends StatelessWidget {
|
||||||
|
final String viewId;
|
||||||
final GridCellBuilder cellBuilder;
|
final GridCellBuilder cellBuilder;
|
||||||
final ScrollController _scrollController;
|
final ScrollController _scrollController;
|
||||||
_PropertyList({
|
_PropertyList({
|
||||||
|
required this.viewId,
|
||||||
required this.cellBuilder,
|
required this.cellBuilder,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : _scrollController = ScrollController(),
|
}) : _scrollController = ScrollController(),
|
||||||
@ -101,7 +110,10 @@ class _PropertyList extends StatelessWidget {
|
|||||||
return BlocBuilder<RowDetailBloc, RowDetailState>(
|
return BlocBuilder<RowDetailBloc, RowDetailState>(
|
||||||
buildWhen: (previous, current) => previous.gridCells != current.gridCells,
|
buildWhen: (previous, current) => previous.gridCells != current.gridCells,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return ScrollbarListStack(
|
return Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: ScrollbarListStack(
|
||||||
axis: Axis.vertical,
|
axis: Axis.vertical,
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
barSize: GridSize.scrollBarSize,
|
barSize: GridSize.scrollBarSize,
|
||||||
@ -118,6 +130,46 @@ class _PropertyList extends StatelessWidget {
|
|||||||
return const VSpace(2);
|
return const VSpace(2);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_CreateFieldButton(viewId: viewId),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CreateFieldButton extends StatelessWidget {
|
||||||
|
final String viewId;
|
||||||
|
const _CreateFieldButton({required this.viewId, Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = context.read<AppTheme>();
|
||||||
|
|
||||||
|
return Popover(
|
||||||
|
triggerActions: PopoverTriggerActionFlags.click,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: FlowyButton(
|
||||||
|
text: FlowyText.medium(
|
||||||
|
LocaleKeys.grid_field_newColumn.tr(),
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
hoverColor: theme.shader6,
|
||||||
|
onTap: () {},
|
||||||
|
leftIcon: svgWidget("home/add"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
popupBuilder: (BuildContext context) {
|
||||||
|
return OverlayContainer(
|
||||||
|
constraints: BoxConstraints.loose(const Size(240, 200)),
|
||||||
|
child: FieldEditor(
|
||||||
|
gridId: viewId,
|
||||||
|
fieldName: "",
|
||||||
|
typeOptionLoader: NewFieldTypeOptionLoader(gridId: viewId),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -150,9 +202,9 @@ class _RowDetailCellState extends State<_RowDetailCell> {
|
|||||||
behavior: HitTestBehavior.translucent,
|
behavior: HitTestBehavior.translucent,
|
||||||
onTap: () => cell.beginFocus.notify(),
|
onTap: () => cell.beginFocus.notify(),
|
||||||
child: AccessoryHover(
|
child: AccessoryHover(
|
||||||
child: cell,
|
|
||||||
contentPadding:
|
contentPadding:
|
||||||
const EdgeInsets.symmetric(horizontal: 10, vertical: 12),
|
const EdgeInsets.symmetric(horizontal: 10, vertical: 12),
|
||||||
|
child: cell,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import 'package:app_flowy/plugins/grid/application/setting/setting_bloc.dart';
|
import 'package:app_flowy/plugins/grid/application/setting/setting_bloc.dart';
|
||||||
import 'package:appflowy_popover/popover.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.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';
|
||||||
|
@ -91,12 +91,12 @@ class _TrashPageState extends State<TrashPage> {
|
|||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return SizedBox.expand(
|
return SizedBox.expand(
|
||||||
child: Column(
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_renderTopBar(context, theme, state),
|
_renderTopBar(context, theme, state),
|
||||||
const VSpace(32),
|
const VSpace(32),
|
||||||
_renderTrashList(context, state),
|
_renderTrashList(context, state),
|
||||||
],
|
],
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
).padding(horizontal: horizontalPadding, vertical: 48),
|
).padding(horizontal: horizontalPadding, vertical: 48),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -20,11 +20,10 @@ class InitAppWidgetTask extends LaunchTask {
|
|||||||
final setting = await UserSettingsService().getAppearanceSettings();
|
final setting = await UserSettingsService().getAppearanceSettings();
|
||||||
final settingModel = AppearanceSettingModel(setting);
|
final settingModel = AppearanceSettingModel(setting);
|
||||||
final app = ApplicationWidget(
|
final app = ApplicationWidget(
|
||||||
child: widget,
|
|
||||||
settingModel: settingModel,
|
settingModel: settingModel,
|
||||||
|
child: widget,
|
||||||
);
|
);
|
||||||
BlocOverrides.runZoned(
|
Bloc.observer = ApplicationBlocObserver();
|
||||||
() {
|
|
||||||
runApp(
|
runApp(
|
||||||
EasyLocalization(
|
EasyLocalization(
|
||||||
supportedLocales: const [
|
supportedLocales: const [
|
||||||
@ -51,9 +50,6 @@ class InitAppWidgetTask extends LaunchTask {
|
|||||||
child: app,
|
child: app,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
|
||||||
blocObserver: ApplicationBlocObserver(),
|
|
||||||
);
|
|
||||||
|
|
||||||
return Future(() => {});
|
return Future(() => {});
|
||||||
}
|
}
|
||||||
|
@ -28,16 +28,19 @@ class AuthRouter {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void pushHomeScreen(BuildContext context, UserProfilePB profile, CurrentWorkspaceSettingPB workspaceSetting) {
|
void pushHomeScreen(BuildContext context, UserProfilePB profile,
|
||||||
|
CurrentWorkspaceSettingPB workspaceSetting) {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
PageRoutes.fade(() => HomeScreen(profile, workspaceSetting), RouteDurations.slow.inMilliseconds * .001),
|
PageRoutes.fade(() => HomeScreen(profile, workspaceSetting),
|
||||||
|
RouteDurations.slow.inMilliseconds * .001),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SplashRoute {
|
class SplashRoute {
|
||||||
Future<void> pushWelcomeScreen(BuildContext context, UserProfilePB userProfile) async {
|
Future<void> pushWelcomeScreen(
|
||||||
|
BuildContext context, UserProfilePB userProfile) async {
|
||||||
final screen = WelcomeScreen(userProfile: userProfile);
|
final screen = WelcomeScreen(userProfile: userProfile);
|
||||||
final workspaceId = await Navigator.of(context).push(
|
final workspaceId = await Navigator.of(context).push(
|
||||||
PageRoutes.fade(
|
PageRoutes.fade(
|
||||||
@ -46,20 +49,24 @@ class SplashRoute {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
pushHomeScreen(context, userProfile, workspaceId);
|
pushHomeScreen(context, userProfile, workspaceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void pushHomeScreen(BuildContext context, UserProfilePB userProfile, CurrentWorkspaceSettingPB workspaceSetting) {
|
void pushHomeScreen(BuildContext context, UserProfilePB userProfile,
|
||||||
|
CurrentWorkspaceSettingPB workspaceSetting) {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
PageRoutes.fade(() => HomeScreen(userProfile, workspaceSetting), RouteDurations.slow.inMilliseconds * .001),
|
PageRoutes.fade(() => HomeScreen(userProfile, workspaceSetting),
|
||||||
|
RouteDurations.slow.inMilliseconds * .001),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void pushSignInScreen(BuildContext context) {
|
void pushSignInScreen(BuildContext context) {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
PageRoutes.fade(() => SignInScreen(router: getIt<AuthRouter>()), RouteDurations.slow.inMilliseconds * .001),
|
PageRoutes.fade(() => SignInScreen(router: getIt<AuthRouter>()),
|
||||||
|
RouteDurations.slow.inMilliseconds * .001),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,6 +94,7 @@ class SignUpPrompt extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = context.watch<AppTheme>();
|
final theme = context.watch<AppTheme>();
|
||||||
return Row(
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(LocaleKeys.signIn_dontHaveAnAccount.tr(), style: TextStyle(color: theme.shader3, fontSize: 12)),
|
Text(LocaleKeys.signIn_dontHaveAnAccount.tr(), style: TextStyle(color: theme.shader3, fontSize: 12)),
|
||||||
TextButton(
|
TextButton(
|
||||||
@ -107,7 +108,6 @@ class SignUpPrompt extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,6 +86,7 @@ class SignUpPrompt extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = context.watch<AppTheme>();
|
final theme = context.watch<AppTheme>();
|
||||||
return Row(
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
LocaleKeys.signUp_alreadyHaveAnAccount.tr(),
|
LocaleKeys.signUp_alreadyHaveAnAccount.tr(),
|
||||||
@ -97,7 +98,6 @@ class SignUpPrompt extends StatelessWidget {
|
|||||||
child: Text(LocaleKeys.signIn_buttonText.tr(), style: TextStyle(color: theme.main1)),
|
child: Text(LocaleKeys.signIn_buttonText.tr(), style: TextStyle(color: theme.main1)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -399,8 +399,8 @@ class AutolinkExtensionSyntax extends InlineSyntax {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DelimiterRun {
|
class DelimiterRun {
|
||||||
_DelimiterRun._(
|
DelimiterRun._(
|
||||||
{this.char,
|
{this.char,
|
||||||
this.length,
|
this.length,
|
||||||
this.isLeftFlanking,
|
this.isLeftFlanking,
|
||||||
@ -420,8 +420,7 @@ class _DelimiterRun {
|
|||||||
final bool? isFollowedByPunctuation;
|
final bool? isFollowedByPunctuation;
|
||||||
|
|
||||||
// ignore: prefer_constructors_over_static_methods
|
// ignore: prefer_constructors_over_static_methods
|
||||||
static _DelimiterRun? tryParse(
|
static DelimiterRun? tryParse(InlineParser parser, int runStart, int runEnd) {
|
||||||
InlineParser parser, int runStart, int runEnd) {
|
|
||||||
bool leftFlanking,
|
bool leftFlanking,
|
||||||
rightFlanking,
|
rightFlanking,
|
||||||
precededByPunctuation,
|
precededByPunctuation,
|
||||||
@ -466,7 +465,7 @@ class _DelimiterRun {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _DelimiterRun._(
|
return DelimiterRun._(
|
||||||
char: parser.charAt(runStart),
|
char: parser.charAt(runStart),
|
||||||
length: runEnd - runStart + 1,
|
length: runEnd - runStart + 1,
|
||||||
isLeftFlanking: leftFlanking,
|
isLeftFlanking: leftFlanking,
|
||||||
@ -516,7 +515,7 @@ class TagSyntax extends InlineSyntax {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
final delimiterRun = _DelimiterRun.tryParse(parser, matchStart, matchEnd);
|
final delimiterRun = DelimiterRun.tryParse(parser, matchStart, matchEnd);
|
||||||
if (delimiterRun != null && delimiterRun.canOpen) {
|
if (delimiterRun != null && delimiterRun.canOpen) {
|
||||||
parser.openTag(TagState(parser.pos, matchEnd + 1, this, delimiterRun));
|
parser.openTag(TagState(parser.pos, matchEnd + 1, this, delimiterRun));
|
||||||
return true;
|
return true;
|
||||||
@ -531,7 +530,7 @@ class TagSyntax extends InlineSyntax {
|
|||||||
final matchStart = parser.pos;
|
final matchStart = parser.pos;
|
||||||
final matchEnd = parser.pos + runLength - 1;
|
final matchEnd = parser.pos + runLength - 1;
|
||||||
final openingRunLength = state.endPos - state.startPos;
|
final openingRunLength = state.endPos - state.startPos;
|
||||||
final delimiterRun = _DelimiterRun.tryParse(parser, matchStart, matchEnd);
|
final delimiterRun = DelimiterRun.tryParse(parser, matchStart, matchEnd);
|
||||||
|
|
||||||
if (openingRunLength == 1 && runLength == 1) {
|
if (openingRunLength == 1 && runLength == 1) {
|
||||||
parser.addNode(Element('em', state.children));
|
parser.addNode(Element('em', state.children));
|
||||||
@ -579,7 +578,7 @@ class StrikethroughSyntax extends TagSyntax {
|
|||||||
final runLength = match.group(0)!.length;
|
final runLength = match.group(0)!.length;
|
||||||
final matchStart = parser.pos;
|
final matchStart = parser.pos;
|
||||||
final matchEnd = parser.pos + runLength - 1;
|
final matchEnd = parser.pos + runLength - 1;
|
||||||
final delimiterRun = _DelimiterRun.tryParse(parser, matchStart, matchEnd)!;
|
final delimiterRun = DelimiterRun.tryParse(parser, matchStart, matchEnd)!;
|
||||||
if (!delimiterRun.isRightFlanking!) {
|
if (!delimiterRun.isRightFlanking!) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1170,7 +1169,7 @@ class TagState {
|
|||||||
/// The children of this node. Will be `null` for text nodes.
|
/// The children of this node. Will be `null` for text nodes.
|
||||||
final List<Node> children;
|
final List<Node> children;
|
||||||
|
|
||||||
final _DelimiterRun? openingDelimiterRun;
|
final DelimiterRun? openingDelimiterRun;
|
||||||
|
|
||||||
/// Attempts to close this tag by matching the current text against its end
|
/// Attempts to close this tag by matching the current text against its end
|
||||||
/// pattern.
|
/// pattern.
|
||||||
@ -1193,7 +1192,7 @@ class TagState {
|
|||||||
final closingMatchStart = parser.pos;
|
final closingMatchStart = parser.pos;
|
||||||
final closingMatchEnd = parser.pos + runLength - 1;
|
final closingMatchEnd = parser.pos + runLength - 1;
|
||||||
final closingDelimiterRun =
|
final closingDelimiterRun =
|
||||||
_DelimiterRun.tryParse(parser, closingMatchStart, closingMatchEnd);
|
DelimiterRun.tryParse(parser, closingMatchStart, closingMatchEnd);
|
||||||
if (closingDelimiterRun != null && closingDelimiterRun.canClose) {
|
if (closingDelimiterRun != null && closingDelimiterRun.canClose) {
|
||||||
// Emphasis rules #9 and #10:
|
// Emphasis rules #9 and #10:
|
||||||
final oneRunOpensAndCloses =
|
final oneRunOpensAndCloses =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import 'package:app_flowy/startup/plugin/plugin.dart';
|
import 'package:app_flowy/startup/plugin/plugin.dart';
|
||||||
import 'package:app_flowy/workspace/application/home/home_bloc.dart';
|
import 'package:app_flowy/workspace/application/home/home_bloc.dart';
|
||||||
|
|
||||||
|
import 'package:app_flowy/workspace/presentation/home/hotkeys.dart';
|
||||||
import 'package:app_flowy/workspace/application/view/view_ext.dart';
|
import 'package:app_flowy/workspace/application/view/view_ext.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/widgets/edit_panel/panel_animation.dart';
|
import 'package:app_flowy/workspace/presentation/widgets/edit_panel/panel_animation.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/widgets/float_bubble/question_bubble.dart';
|
import 'package:app_flowy/workspace/presentation/widgets/float_bubble/question_bubble.dart';
|
||||||
@ -54,6 +56,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
child: HomeHotKeys(
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
body: BlocListener<HomeBloc, HomeState>(
|
body: BlocListener<HomeBloc, HomeState>(
|
||||||
listenWhen: (p, c) => p.unauthorized != c.unauthorized,
|
listenWhen: (p, c) => p.unauthorized != c.unauthorized,
|
||||||
@ -80,7 +83,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,6 +148,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
return FocusTraversalGroup(child: RepaintBoundary(child: homeMenu));
|
return FocusTraversalGroup(child: RepaintBoundary(child: homeMenu));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Widget _buildEditPanel(
|
Widget _buildEditPanel(
|
||||||
{required HomeState homeState,
|
{required HomeState homeState,
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
|
@ -58,10 +58,10 @@ class FadingIndexedStack extends StatefulWidget {
|
|||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_FadingIndexedStackState createState() => _FadingIndexedStackState();
|
FadingIndexedStackState createState() => FadingIndexedStackState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FadingIndexedStackState extends State<FadingIndexedStack> {
|
class FadingIndexedStackState extends State<FadingIndexedStack> {
|
||||||
double _targetOpacity = 1;
|
double _targetOpacity = 1;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:app_flowy/startup/startup.dart';
|
||||||
|
import 'package:app_flowy/workspace/application/home/home_bloc.dart';
|
||||||
|
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hotkey_manager/hotkey_manager.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class HomeHotKeys extends StatelessWidget {
|
||||||
|
final Widget child;
|
||||||
|
const HomeHotKeys({required this.child, Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
HotKey hotKey = HotKey(
|
||||||
|
KeyCode.backslash,
|
||||||
|
modifiers: [Platform.isMacOS ? KeyModifier.meta : KeyModifier.control],
|
||||||
|
// Set hotkey scope (default is HotKeyScope.system)
|
||||||
|
scope: HotKeyScope.inapp, // Set as inapp-wide hotkey.
|
||||||
|
);
|
||||||
|
hotKeyManager.register(
|
||||||
|
hotKey,
|
||||||
|
keyDownHandler: (hotKey) {
|
||||||
|
context.read<HomeBloc>().add(const HomeEvent.collapseMenu());
|
||||||
|
getIt<HomeStackManager>().collapsedNotifier.value =
|
||||||
|
!getIt<HomeStackManager>().collapsedNotifier.currentValue!;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
}
|
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