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 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 keybinder-3.0
|
||||
elif [ "$RUNNER_OS" == "macOS" ]; then
|
||||
echo 'do nothing'
|
||||
fi
|
||||
|
4
.github/workflows/flowy_editor_test.yml
vendored
4
.github/workflows/flowy_editor_test.yml
vendored
@ -4,10 +4,14 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
paths:
|
||||
- "frontend/app_flowy/packages/appflowy_editor"
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- "main"
|
||||
paths:
|
||||
- "frontend/app_flowy/packages/appflowy_editor"
|
||||
|
||||
env:
|
||||
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 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 keybinder-3.0
|
||||
source $HOME/.cargo/env
|
||||
cargo install --force cargo-make
|
||||
cargo install --force duckscript_cli
|
||||
|
37
CHANGELOG.md
37
CHANGELOG.md
@ -1,6 +1,29 @@
|
||||
# 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
|
||||
- Upgrade to Flutter 3.0
|
||||
- Native support for M1 chip
|
||||
@ -12,12 +35,12 @@
|
||||
- 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
|
||||
- Row record open as a page
|
||||
- Auto resize the height of the row in the grid
|
||||
- Support more number formats
|
||||
- Search column options, supporting Single select, Multi-select, and number format
|
||||
- Search column options, supporting Single-select, Multi-select, and number format
|
||||
|
||||

|
||||
|
||||
@ -27,7 +50,7 @@
|
||||
- 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
|
||||
- Insert / delete rows
|
||||
@ -35,16 +58,16 @@
|
||||
- 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
|
||||
|
||||
New features
|
||||
- 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
|
||||
- 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
|
||||
|
||||
New features
|
||||
|
38
frontend/.vscode/launch.json
vendored
38
frontend/.vscode/launch.json
vendored
@ -16,6 +16,18 @@
|
||||
},
|
||||
"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",
|
||||
"request": "attach",
|
||||
@ -48,6 +60,21 @@
|
||||
},
|
||||
"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)",
|
||||
"request": "launch",
|
||||
@ -59,6 +86,17 @@
|
||||
},
|
||||
"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)",
|
||||
"request": "launch",
|
||||
|
27
frontend/.vscode/tasks.json
vendored
27
frontend/.vscode/tasks.json
vendored
@ -27,6 +27,33 @@
|
||||
"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",
|
||||
"type": "shell",
|
||||
|
@ -22,7 +22,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
|
||||
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
|
||||
CARGO_MAKE_CRATE_NAME = "dart-ffi"
|
||||
LIB_NAME = "dart_ffi"
|
||||
CURRENT_APP_VERSION = "0.0.4"
|
||||
CURRENT_APP_VERSION = "0.0.5"
|
||||
FEATURES = "flutter"
|
||||
PRODUCT_NAME = "AppFlowy"
|
||||
# CRATE_TYPE: https://doc.rust-lang.org/reference/linkage.html
|
||||
@ -161,6 +161,11 @@ TARGET_OS = "ios"
|
||||
FLUTTER_OUTPUT_DIR = "Release"
|
||||
PRODUCT_EXT = "ipa"
|
||||
|
||||
[env.development-android]
|
||||
BUILD_FLAG = "debug"
|
||||
TARGET_OS = "android"
|
||||
CRATE_TYPE = "cdylib"
|
||||
FLUTTER_OUTPUT_DIR = "Debug"
|
||||
|
||||
[tasks.setup-crate-type]
|
||||
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"
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
compileSdkVersion 31
|
||||
ndkVersion "24.0.8215888"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
@ -39,21 +40,26 @@ android {
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
main.jniLibs.srcDirs += 'jniLibs/'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "com.example.app_flowy"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 30
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 31
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
multiDexEnabled true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
}
|
||||
@ -65,4 +71,5 @@ flutter {
|
||||
|
||||
dependencies {
|
||||
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">
|
||||
<application
|
||||
android:label="app_flowy"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:name="${applicationName}">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:launchMode="singleTop"
|
||||
|
@ -1,5 +1,5 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.3.50'
|
||||
ext.kotlin_version = '1.6.10'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
|
@ -1,3 +1,4 @@
|
||||
org.gradle.jvmargs=-Xmx1536M
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
org.gradle.caching=true
|
||||
|
@ -1,6 +1,5 @@
|
||||
#Fri Jun 23 08:50:38 CEST 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
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")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
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",
|
||||
"darkLabel": "Mode Fosc"
|
||||
}
|
||||
},
|
||||
"sideBar": {
|
||||
"openSidebar": "Open sidebar",
|
||||
"closeSidebar": "Close sidebar"
|
||||
}
|
||||
}
|
||||
|
@ -141,6 +141,10 @@
|
||||
"lightLabel": "Heller Modus",
|
||||
"darkLabel": "Dunkler Modus"
|
||||
}
|
||||
},
|
||||
"sideBar": {
|
||||
"openSidebar": "Open sidebar",
|
||||
"closeSidebar": "Close sidebar"
|
||||
}
|
||||
}
|
||||
|
@ -95,7 +95,13 @@
|
||||
"tooltip": {
|
||||
"lightMode": "Switch to Light 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": {
|
||||
"export": {
|
||||
@ -183,7 +189,8 @@
|
||||
"addSelectOption": "Add an option",
|
||||
"optionTitle": "Options",
|
||||
"addOption": "Add option",
|
||||
"editProperty": "Edit property"
|
||||
"editProperty": "Edit property",
|
||||
"newColumn": "New column"
|
||||
},
|
||||
"row": {
|
||||
"duplicate": "Duplicate",
|
||||
@ -215,5 +222,10 @@
|
||||
"timeHintTextInTwelveHour": "12:00 AM",
|
||||
"timeHintTextInTwentyFourHour": "12:00"
|
||||
}
|
||||
},
|
||||
"board": {
|
||||
"column": {
|
||||
"create_new_card": "New"
|
||||
}
|
||||
}
|
||||
}
|
@ -213,5 +213,9 @@
|
||||
"timeHintTextInTwelveHour": "12:00 AM",
|
||||
"timeHintTextInTwentyFourHour": "12:00"
|
||||
}
|
||||
},
|
||||
"sideBar": {
|
||||
"openSidebar": "Open sidebar",
|
||||
"closeSidebar": "Close sidebar"
|
||||
}
|
||||
}
|
||||
|
@ -141,5 +141,9 @@
|
||||
"lightLabel": "Mode clair",
|
||||
"darkLabel": "Mode sombre"
|
||||
}
|
||||
},
|
||||
"sideBar": {
|
||||
"openSidebar": "Open sidebar",
|
||||
"closeSidebar": "Close sidebar"
|
||||
}
|
||||
}
|
||||
|
@ -142,6 +142,10 @@
|
||||
"darkLabel": "Mode sombre"
|
||||
}
|
||||
},
|
||||
"sideBar": {
|
||||
"openSidebar": "Open sidebar",
|
||||
"closeSidebar": "Close sidebar"
|
||||
},
|
||||
"grid": {
|
||||
"settings": {
|
||||
"filter": "Filtrer",
|
||||
|
@ -141,5 +141,9 @@
|
||||
"lightLabel": "Világos mód",
|
||||
"darkLabel": "Éjjeli mód"
|
||||
}
|
||||
},
|
||||
"sideBar": {
|
||||
"openSidebar": "Open sidebar",
|
||||
"closeSidebar": "Close sidebar"
|
||||
}
|
||||
}
|
||||
|
@ -214,5 +214,9 @@
|
||||
"timeHintTextInTwelveHour": "12:00 AM",
|
||||
"timeHintTextInTwentyFourHour": "12:00"
|
||||
}
|
||||
},
|
||||
"sideBar": {
|
||||
"openSidebar": "Open sidebar",
|
||||
"closeSidebar": "Close sidebar"
|
||||
}
|
||||
}
|
@ -147,5 +147,9 @@
|
||||
},
|
||||
"document":{
|
||||
"menuName":"Documento"
|
||||
},
|
||||
"sideBar": {
|
||||
"openSidebar": "Open sidebar",
|
||||
"closeSidebar": "Close sidebar"
|
||||
}
|
||||
}
|
||||
|
@ -195,5 +195,9 @@
|
||||
"pannelTitle": "選択候補を検索 または 作成する",
|
||||
"searchOption": "選択候補を検索"
|
||||
}
|
||||
},
|
||||
"sideBar": {
|
||||
"openSidebar": "Open sidebar",
|
||||
"closeSidebar": "Close sidebar"
|
||||
}
|
||||
}
|
@ -141,5 +141,9 @@
|
||||
"lightLabel": "Tryb Jasny",
|
||||
"darkLabel": "Tryb Ciemny"
|
||||
}
|
||||
},
|
||||
"sideBar": {
|
||||
"openSidebar": "Open sidebar",
|
||||
"closeSidebar": "Close sidebar"
|
||||
}
|
||||
}
|
||||
|
@ -141,6 +141,10 @@
|
||||
"lightLabel": "Modo Claro",
|
||||
"darkLabel": "Modo Escuro"
|
||||
}
|
||||
},
|
||||
"sideBar": {
|
||||
"openSidebar": "Open sidebar",
|
||||
"closeSidebar": "Close sidebar"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,6 +141,10 @@
|
||||
"lightLabel": "Modo Claro",
|
||||
"darkLabel": "Modo Escuro"
|
||||
}
|
||||
},
|
||||
"sideBar": {
|
||||
"openSidebar": "Open sidebar",
|
||||
"closeSidebar": "Close sidebar"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -203,6 +203,10 @@
|
||||
"timeHintTextInTwelveHour": "12:00 AM",
|
||||
"timeHintTextInTwentyFourHour": "12:00"
|
||||
}
|
||||
},
|
||||
"sideBar": {
|
||||
"openSidebar": "Open sidebar",
|
||||
"closeSidebar": "Close sidebar"
|
||||
}
|
||||
}
|
||||
|
@ -141,5 +141,9 @@
|
||||
"lightLabel": "Aydınlık Mod",
|
||||
"darkLabel": "Karanlık Mod"
|
||||
}
|
||||
},
|
||||
"sideBar": {
|
||||
"openSidebar": "Open sidebar",
|
||||
"closeSidebar": "Close sidebar"
|
||||
}
|
||||
}
|
||||
|
@ -93,8 +93,14 @@
|
||||
"highlight": "高亮"
|
||||
},
|
||||
"tooltip": {
|
||||
"lightMode": "切换到灯光模式",
|
||||
"darkMode": "切换到暗模式"
|
||||
"lightMode": "切换到亮色模式",
|
||||
"darkMode": "切换到暗色模式"
|
||||
},
|
||||
"notifications": {
|
||||
"export": {
|
||||
"markdown": "导出笔记为Markdown文档",
|
||||
"path": "Documents/flowy"
|
||||
}
|
||||
},
|
||||
"contactsPage": {
|
||||
"title": "联系人",
|
||||
@ -135,11 +141,82 @@
|
||||
"menu": {
|
||||
"appearance": "外观",
|
||||
"language": "语言",
|
||||
"user": "用户",
|
||||
"open": "打开设置"
|
||||
},
|
||||
"appearance": {
|
||||
"lightLabel": "日间模式",
|
||||
"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",
|
||||
"timeHintTextInTwentyFourHour": "12:00"
|
||||
}
|
||||
},
|
||||
"sideBar": {
|
||||
"openSidebar": "Open sidebar",
|
||||
"closeSidebar": "Close sidebar"
|
||||
}
|
||||
}
|
@ -31,10 +31,10 @@ class MoveWindowDetector extends StatefulWidget {
|
||||
final Widget? child;
|
||||
|
||||
@override
|
||||
_MoveWindowDetectorState createState() => _MoveWindowDetectorState();
|
||||
MoveWindowDetectorState createState() => MoveWindowDetectorState();
|
||||
}
|
||||
|
||||
class _MoveWindowDetectorState extends State<MoveWindowDetector> {
|
||||
class MoveWindowDetectorState extends State<MoveWindowDetector> {
|
||||
double winX = 0;
|
||||
double winY = 0;
|
||||
|
||||
@ -59,7 +59,8 @@ class _MoveWindowDetectorState extends State<MoveWindowDetector> {
|
||||
final double dy = windowPos[1];
|
||||
final deltaX = details.globalPosition.dx - winX;
|
||||
final deltaY = details.globalPosition.dy - winY;
|
||||
await CocoaWindowChannel.instance.setWindowPosition(Offset(dx + deltaX, dy - deltaY));
|
||||
await CocoaWindowChannel.instance
|
||||
.setWindowPosition(Offset(dx + deltaX, dy - deltaY));
|
||||
},
|
||||
child: widget.child,
|
||||
);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/user/presentation/splash_screen.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:hotkey_manager/hotkey_manager.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class FlowyApp implements EntryPoint {
|
||||
@ -14,5 +15,8 @@ void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await EasyLocalization.ensureInitialized();
|
||||
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await hotKeyManager.unregisterAll();
|
||||
|
||||
await FlowyRunner.run(FlowyApp());
|
||||
}
|
||||
|
@ -20,19 +20,19 @@ import 'group_controller.dart';
|
||||
part 'board_bloc.freezed.dart';
|
||||
|
||||
class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
final BoardDataController _dataController;
|
||||
late final AFBoardDataController afBoardDataController;
|
||||
final BoardDataController _gridDataController;
|
||||
late final AFBoardDataController boardController;
|
||||
final MoveRowFFIService _rowService;
|
||||
LinkedHashMap<String, GroupController> groupControllers = LinkedHashMap.new();
|
||||
LinkedHashMap<String, GroupController> groupControllers = LinkedHashMap();
|
||||
|
||||
GridFieldCache get fieldCache => _dataController.fieldCache;
|
||||
String get gridId => _dataController.gridId;
|
||||
GridFieldCache get fieldCache => _gridDataController.fieldCache;
|
||||
String get gridId => _gridDataController.gridId;
|
||||
|
||||
BoardBloc({required ViewPB view})
|
||||
: _rowService = MoveRowFFIService(gridId: view.id),
|
||||
_dataController = BoardDataController(view: view),
|
||||
_gridDataController = BoardDataController(view: view),
|
||||
super(BoardState.initial(view.id)) {
|
||||
afBoardDataController = AFBoardDataController(
|
||||
boardController = AFBoardDataController(
|
||||
onMoveColumn: (
|
||||
fromColumnId,
|
||||
fromIndex,
|
||||
@ -69,31 +69,51 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
_startListening();
|
||||
await _loadGrid(emit);
|
||||
},
|
||||
createRow: (groupId) async {
|
||||
final result = await _dataController.createBoardCard(groupId);
|
||||
createBottomRow: (groupId) async {
|
||||
final startRowId = groupControllers[groupId]?.lastRow()?.id;
|
||||
final result = await _gridDataController.createBoardCard(
|
||||
groupId,
|
||||
startRowId: startRowId,
|
||||
);
|
||||
result.fold(
|
||||
(rowPB) {
|
||||
emit(state.copyWith(editingRow: some(rowPB)));
|
||||
},
|
||||
(_) {},
|
||||
(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) {
|
||||
assert(state.editingRow.isSome());
|
||||
state.editingRow.fold(() => null, (row) {
|
||||
assert(row.id == rowId);
|
||||
state.editingRow.fold(() => null, (editingRow) {
|
||||
assert(editingRow.row.id == rowId);
|
||||
emit(state.copyWith(editingRow: none()));
|
||||
});
|
||||
},
|
||||
didReceiveGridUpdate: (GridPB grid) {
|
||||
emit(state.copyWith(grid: Some(grid)));
|
||||
},
|
||||
didReceiveRows: (List<RowInfo> rowInfos) {
|
||||
emit(state.copyWith(rowInfos: rowInfos));
|
||||
},
|
||||
didReceiveError: (FlowyError error) {
|
||||
emit(state.copyWith(noneOrError: some(error)));
|
||||
},
|
||||
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
|
||||
Future<void> close() async {
|
||||
await _dataController.dispose();
|
||||
await _gridDataController.dispose();
|
||||
for (final controller in groupControllers.values) {
|
||||
controller.dispose();
|
||||
}
|
||||
@ -135,7 +155,12 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
|
||||
void initializeGroups(List<GroupPB> 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(
|
||||
gridId: state.gridId,
|
||||
group: group,
|
||||
@ -147,12 +172,12 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
}
|
||||
|
||||
GridRowCache? getRowCache(String blockId) {
|
||||
final GridBlockCache? blockCache = _dataController.blocks[blockId];
|
||||
final GridBlockCache? blockCache = _gridDataController.blocks[blockId];
|
||||
return blockCache?.rowCache;
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_dataController.addListener(
|
||||
_gridDataController.addListener(
|
||||
onGridChanged: (grid) {
|
||||
if (!isClosed) {
|
||||
add(BoardEvent.didReceiveGridUpdate(grid));
|
||||
@ -162,17 +187,31 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
List<AFBoardColumnData> columns = groups.map((group) {
|
||||
return AFBoardColumnData(
|
||||
id: group.groupId,
|
||||
desc: group.desc,
|
||||
items: _buildRows(group.rows),
|
||||
name: group.desc,
|
||||
items: _buildRows(group),
|
||||
customData: group,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
afBoardDataController.addColumns(columns);
|
||||
boardController.addColumns(columns);
|
||||
initializeGroups(groups);
|
||||
add(BoardEvent.didReceiveGroups(groups));
|
||||
},
|
||||
onRowsChanged: (List<RowInfo> rowInfos, RowsChangedReason reason) {
|
||||
add(BoardEvent.didReceiveRows(rowInfos));
|
||||
onDeletedGroup: (groupIds) {
|
||||
//
|
||||
},
|
||||
onInsertedGroup: (insertedGroups) {
|
||||
//
|
||||
},
|
||||
onUpdatedGroup: (updatedGroups) {
|
||||
//
|
||||
for (final group in updatedGroups) {
|
||||
final columnController =
|
||||
boardController.getColumnController(group.groupId);
|
||||
if (columnController != null) {
|
||||
columnController.updateColumnName(group.desc);
|
||||
}
|
||||
}
|
||||
},
|
||||
onError: (err) {
|
||||
Log.error(err);
|
||||
@ -180,16 +219,19 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
);
|
||||
}
|
||||
|
||||
List<AFColumnItem> _buildRows(List<RowPB> rows) {
|
||||
final items = rows.map((row) {
|
||||
return BoardColumnItem(row: row);
|
||||
List<AFColumnItem> _buildRows(GroupPB group) {
|
||||
final items = group.rows.map((row) {
|
||||
return BoardColumnItem(
|
||||
row: row,
|
||||
fieldId: group.fieldId,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
return <AFColumnItem>[...items];
|
||||
}
|
||||
|
||||
Future<void> _loadGrid(Emitter<BoardState> emit) async {
|
||||
final result = await _dataController.loadData();
|
||||
final result = await _gridDataController.loadData();
|
||||
result.fold(
|
||||
(grid) => emit(
|
||||
state.copyWith(loadingState: GridLoadingState.finish(left(unit))),
|
||||
@ -203,15 +245,21 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
|
||||
@freezed
|
||||
class BoardEvent with _$BoardEvent {
|
||||
const factory BoardEvent.initial() = InitialGrid;
|
||||
const factory BoardEvent.createRow(String groupId) = _CreateRow;
|
||||
const factory BoardEvent.initial() = _InitialBoard;
|
||||
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.didReceiveError(FlowyError error) = _DidReceiveError;
|
||||
const factory BoardEvent.didReceiveRows(List<RowInfo> rowInfos) =
|
||||
_DidReceiveRows;
|
||||
const factory BoardEvent.didReceiveGridUpdate(
|
||||
GridPB grid,
|
||||
) = _DidReceiveGridUpdate;
|
||||
const factory BoardEvent.didReceiveGroups(List<GroupPB> groups) =
|
||||
_DidReceiveGroups;
|
||||
}
|
||||
|
||||
@freezed
|
||||
@ -219,16 +267,16 @@ class BoardState with _$BoardState {
|
||||
const factory BoardState({
|
||||
required String gridId,
|
||||
required Option<GridPB> grid,
|
||||
required Option<RowPB> editingRow,
|
||||
required List<RowInfo> rowInfos,
|
||||
required List<String> groupIds,
|
||||
required Option<BoardEditingRow> editingRow,
|
||||
required GridLoadingState loadingState,
|
||||
required Option<FlowyError> noneOrError,
|
||||
}) = _BoardState;
|
||||
|
||||
factory BoardState.initial(String gridId) => BoardState(
|
||||
rowInfos: [],
|
||||
grid: none(),
|
||||
gridId: gridId,
|
||||
groupIds: [],
|
||||
editingRow: none(),
|
||||
noneOrError: none(),
|
||||
loadingState: const _Loading(),
|
||||
@ -268,39 +316,84 @@ class GridFieldEquatable extends Equatable {
|
||||
class BoardColumnItem extends AFColumnItem {
|
||||
final RowPB row;
|
||||
|
||||
BoardColumnItem({required this.row});
|
||||
final String fieldId;
|
||||
|
||||
final bool requestFocus;
|
||||
|
||||
BoardColumnItem({
|
||||
required this.row,
|
||||
required this.fieldId,
|
||||
this.requestFocus = false,
|
||||
});
|
||||
|
||||
@override
|
||||
String get id => row.id;
|
||||
}
|
||||
|
||||
class CreateCardItem extends AFColumnItem {
|
||||
@override
|
||||
String get id => '$CreateCardItem';
|
||||
}
|
||||
|
||||
class GroupControllerDelegateImpl extends GroupControllerDelegate {
|
||||
final AFBoardDataController controller;
|
||||
final void Function(String, RowPB, int?) onNewColumnItem;
|
||||
|
||||
GroupControllerDelegateImpl(this.controller);
|
||||
GroupControllerDelegateImpl({
|
||||
required this.controller,
|
||||
required this.onNewColumnItem,
|
||||
});
|
||||
|
||||
@override
|
||||
void insertRow(String groupId, RowPB row, int? index) {
|
||||
final item = BoardColumnItem(row: row);
|
||||
void insertRow(GroupPB group, RowPB row, int? index) {
|
||||
if (index != null) {
|
||||
controller.insertColumnItem(groupId, index, item);
|
||||
final item = BoardColumnItem(row: row, fieldId: group.fieldId);
|
||||
controller.insertColumnItem(group.groupId, index, item);
|
||||
} else {
|
||||
controller.addColumnItem(groupId, item);
|
||||
final item = BoardColumnItem(
|
||||
row: row,
|
||||
fieldId: group.fieldId,
|
||||
);
|
||||
controller.addColumnItem(group.groupId, item);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void removeRow(String groupId, String rowId) {
|
||||
controller.removeColumnItem(groupId, rowId);
|
||||
void removeRow(GroupPB group, String rowId) {
|
||||
controller.removeColumnItem(group.groupId, rowId);
|
||||
}
|
||||
|
||||
@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:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
|
||||
|
||||
import 'board_listener.dart';
|
||||
|
||||
typedef OnFieldsChanged = void Function(UnmodifiableListView<FieldPB>);
|
||||
typedef OnGridChanged = void Function(GridPB);
|
||||
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(
|
||||
List<RowInfo>,
|
||||
RowsChangedReason,
|
||||
@ -23,6 +29,7 @@ class BoardDataController {
|
||||
final String gridId;
|
||||
final GridFFIService _gridFFIService;
|
||||
final GridFieldCache fieldCache;
|
||||
final BoardListener _listener;
|
||||
|
||||
// key: the block id
|
||||
final LinkedHashMap<String, GridBlockCache> _blocks;
|
||||
@ -44,16 +51,21 @@ class BoardDataController {
|
||||
|
||||
BoardDataController({required ViewPB view})
|
||||
: gridId = view.id,
|
||||
_blocks = LinkedHashMap.new(),
|
||||
_listener = BoardListener(view.id),
|
||||
// ignore: prefer_collection_literals
|
||||
_blocks = LinkedHashMap(),
|
||||
_gridFFIService = GridFFIService(gridId: view.id),
|
||||
fieldCache = GridFieldCache(gridId: view.id);
|
||||
|
||||
void addListener({
|
||||
OnGridChanged? onGridChanged,
|
||||
required OnGridChanged onGridChanged,
|
||||
OnFieldsChanged? onFieldsChanged,
|
||||
DidLoadGroups? didLoadGroups,
|
||||
required DidLoadGroups didLoadGroups,
|
||||
OnRowsChanged? onRowsChanged,
|
||||
OnError? onError,
|
||||
required OnUpdatedGroup onUpdatedGroup,
|
||||
required OnDeletedGroup onDeletedGroup,
|
||||
required OnInsertedGroup onInsertedGroup,
|
||||
required OnError? onError,
|
||||
}) {
|
||||
_onGridChanged = onGridChanged;
|
||||
_onFieldsChanged = onFieldsChanged;
|
||||
@ -64,6 +76,25 @@ class BoardDataController {
|
||||
fieldCache.addListener(onFields: (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 {
|
||||
@ -88,8 +119,9 @@ class BoardDataController {
|
||||
);
|
||||
}
|
||||
|
||||
Future<Either<RowPB, FlowyError>> createBoardCard(String groupId) {
|
||||
return _gridFFIService.createBoardCard(groupId);
|
||||
Future<Either<RowPB, FlowyError>> createBoardCard(String groupId,
|
||||
{String? startRowId}) {
|
||||
return _gridFFIService.createBoardCard(groupId, startRowId);
|
||||
}
|
||||
|
||||
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 dateStr = "";
|
||||
if (cellData != null) {
|
||||
dateStr = cellData.date + " " + cellData.time;
|
||||
dateStr = "${cellData.date} ${cellData.time}";
|
||||
}
|
||||
return dateStr;
|
||||
}
|
||||
|
@ -68,7 +68,6 @@ class BoardSelectOptionCellState with _$BoardSelectOptionCellState {
|
||||
factory BoardSelectOptionCellState.initial(
|
||||
GridSelectOptionCellController context) {
|
||||
final data = context.getCellData();
|
||||
|
||||
return BoardSelectOptionCellState(
|
||||
selectedOptions: data?.selectOptions ?? [],
|
||||
);
|
||||
|
@ -1,4 +1,5 @@
|
||||
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:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
@ -20,6 +21,15 @@ class BoardTextCellBloc extends Bloc<BoardTextCellEvent, BoardTextCellState> {
|
||||
didReceiveCellUpdate: (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
|
||||
class BoardTextCellEvent with _$BoardTextCellEvent {
|
||||
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) =
|
||||
_DidReceiveCellUpdate;
|
||||
}
|
||||
@ -57,10 +69,12 @@ class BoardTextCellEvent with _$BoardTextCellEvent {
|
||||
class BoardTextCellState with _$BoardTextCellState {
|
||||
const factory BoardTextCellState({
|
||||
required String content,
|
||||
required bool enableEdit,
|
||||
}) = _BoardTextCellState;
|
||||
|
||||
factory BoardTextCellState.initial(GridCellController context) =>
|
||||
BoardTextCellState(
|
||||
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:equatable/equatable.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
@ -14,10 +13,12 @@ import 'card_data_controller.dart';
|
||||
part 'card_bloc.freezed.dart';
|
||||
|
||||
class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
|
||||
final String fieldId;
|
||||
final RowFFIService _rowService;
|
||||
final CardDataController _dataController;
|
||||
|
||||
BoardCardBloc({
|
||||
required this.fieldId,
|
||||
required String gridId,
|
||||
required CardDataController dataController,
|
||||
}) : _rowService = RowFFIService(
|
||||
@ -25,22 +26,22 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
|
||||
blockId: dataController.rowPB.blockId,
|
||||
),
|
||||
_dataController = dataController,
|
||||
super(BoardCardState.initial(
|
||||
dataController.rowPB, dataController.loadData())) {
|
||||
super(
|
||||
BoardCardState.initial(
|
||||
dataController.rowPB,
|
||||
_makeCells(fieldId, dataController.loadData()),
|
||||
),
|
||||
) {
|
||||
on<BoardCardEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
initial: (_InitialRow value) async {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
await _startListening();
|
||||
},
|
||||
didReceiveCells: (_DidReceiveCells value) async {
|
||||
final cells = value.gridCellMap.values
|
||||
.map((e) => GridCellEquatable(e.field))
|
||||
.toList();
|
||||
didReceiveCells: (cells, reason) async {
|
||||
emit(state.copyWith(
|
||||
gridCellMap: value.gridCellMap,
|
||||
cells: UnmodifiableListView(cells),
|
||||
changeReason: value.reason,
|
||||
cells: cells,
|
||||
changeReason: reason,
|
||||
));
|
||||
},
|
||||
);
|
||||
@ -58,7 +59,7 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
|
||||
return RowInfo(
|
||||
gridId: _rowService.gridId,
|
||||
fields: UnmodifiableListView(
|
||||
state.cells.map((cell) => cell._field).toList(),
|
||||
state.cells.map((cell) => cell.identifier.field).toList(),
|
||||
),
|
||||
rowPB: state.rowPB,
|
||||
);
|
||||
@ -66,8 +67,9 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
|
||||
|
||||
Future<void> _startListening() async {
|
||||
_dataController.addListener(
|
||||
onRowChanged: (cells, reason) {
|
||||
onRowChanged: (cellMap, reason) {
|
||||
if (!isClosed) {
|
||||
final cells = _makeCells(fieldId, cellMap);
|
||||
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
|
||||
class BoardCardEvent with _$BoardCardEvent {
|
||||
const factory BoardCardEvent.initial() = _InitialRow;
|
||||
const factory BoardCardEvent.didReceiveCells(
|
||||
GridCellMap gridCellMap, RowsChangedReason reason) = _DidReceiveCells;
|
||||
UnmodifiableListView<BoardCellEquatable> cells,
|
||||
RowsChangedReason reason,
|
||||
) = _DidReceiveCells;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class BoardCardState with _$BoardCardState {
|
||||
const factory BoardCardState({
|
||||
required RowPB rowPB,
|
||||
required GridCellMap gridCellMap,
|
||||
required UnmodifiableListView<GridCellEquatable> cells,
|
||||
required UnmodifiableListView<BoardCellEquatable> cells,
|
||||
RowsChangedReason? changeReason,
|
||||
}) = _BoardCardState;
|
||||
|
||||
factory BoardCardState.initial(RowPB rowPB, GridCellMap cellDataMap) =>
|
||||
factory BoardCardState.initial(
|
||||
RowPB rowPB, UnmodifiableListView<BoardCellEquatable> cells) =>
|
||||
BoardCardState(
|
||||
rowPB: rowPB,
|
||||
gridCellMap: cellDataMap,
|
||||
cells: UnmodifiableListView(
|
||||
cellDataMap.values.map((e) => GridCellEquatable(e.field)).toList(),
|
||||
),
|
||||
cells: cells,
|
||||
);
|
||||
}
|
||||
|
||||
class GridCellEquatable extends Equatable {
|
||||
final FieldPB _field;
|
||||
class BoardCellEquatable extends Equatable {
|
||||
final GridCellIdentifier identifier;
|
||||
|
||||
const GridCellEquatable(FieldPB field) : _field = field;
|
||||
const BoardCellEquatable(this.identifier);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
_field.id,
|
||||
_field.fieldType,
|
||||
_field.visibility,
|
||||
_field.width,
|
||||
identifier.field.id,
|
||||
identifier.field.fieldType,
|
||||
identifier.field.visibility,
|
||||
identifier.field.width,
|
||||
];
|
||||
}
|
||||
|
@ -1,15 +1,15 @@
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
|
||||
|
||||
import 'group_listener.dart';
|
||||
|
||||
typedef OnGroupError = void Function(FlowyError);
|
||||
|
||||
abstract class GroupControllerDelegate {
|
||||
void removeRow(String groupId, String rowId);
|
||||
void insertRow(String groupId, RowPB row, int? index);
|
||||
void updateRow(String groupId, RowPB row);
|
||||
void removeRow(GroupPB group, String rowId);
|
||||
void insertRow(GroupPB group, RowPB row, int? index);
|
||||
void updateRow(GroupPB group, RowPB row);
|
||||
void addNewRow(GroupPB group, RowPB row, int? index);
|
||||
}
|
||||
|
||||
class GroupController {
|
||||
@ -31,13 +31,22 @@ class GroupController {
|
||||
}
|
||||
}
|
||||
|
||||
RowPB? lastRow() {
|
||||
if (group.rows.isEmpty) return null;
|
||||
return group.rows.last;
|
||||
}
|
||||
|
||||
void startListening() {
|
||||
_listener.start(onGroupChanged: (result) {
|
||||
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) {
|
||||
final index = insertedRow.hasIndex() ? insertedRow.index : null;
|
||||
|
||||
if (insertedRow.hasIndex() &&
|
||||
group.rows.length > insertedRow.index) {
|
||||
group.rows.insert(insertedRow.index, insertedRow.row);
|
||||
@ -45,16 +54,11 @@ class GroupController {
|
||||
group.rows.add(insertedRow.row);
|
||||
}
|
||||
|
||||
delegate.insertRow(
|
||||
group.groupId,
|
||||
insertedRow.row,
|
||||
index,
|
||||
);
|
||||
if (insertedRow.isNew) {
|
||||
delegate.addNewRow(group, insertedRow.row, index);
|
||||
} else {
|
||||
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) {
|
||||
@ -66,7 +70,7 @@ class GroupController {
|
||||
group.rows[index] = updatedRow;
|
||||
}
|
||||
|
||||
delegate.updateRow(group.groupId, updatedRow);
|
||||
delegate.updateRow(group, updatedRow);
|
||||
}
|
||||
},
|
||||
(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 {
|
||||
_listener.stop();
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid/group.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/group_changeset.pb.dart';
|
||||
|
||||
typedef UpdateGroupNotifiedValue = Either<GroupRowsChangesetPB, FlowyError>;
|
||||
typedef UpdateGroupNotifiedValue = Either<GroupChangesetPB, FlowyError>;
|
||||
|
||||
class GroupListener {
|
||||
final GroupPB group;
|
||||
@ -34,7 +34,7 @@ class GroupListener {
|
||||
case GridNotification.DidUpdateGroup:
|
||||
result.fold(
|
||||
(payload) => _groupNotifier?.value =
|
||||
left(GroupRowsChangesetPB.fromBuffer(payload)),
|
||||
left(GroupChangesetPB.fromBuffer(payload)),
|
||||
(error) => _groupNotifier?.value = right(error),
|
||||
);
|
||||
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 {
|
||||
@override
|
||||
bool get creatable => false;
|
||||
bool get creatable => true;
|
||||
}
|
||||
|
||||
class BoardPlugin extends Plugin {
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
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/grid/application/row/row_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/row/row_detail.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/widget/error_page.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/group.pbserver.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../grid/application/row/row_cache.dart';
|
||||
import '../application/board_bloc.dart';
|
||||
import 'card/card.dart';
|
||||
import 'card/card_cell_builder.dart';
|
||||
import 'toolbar/board_toolbar.dart';
|
||||
|
||||
class BoardPage extends StatelessWidget {
|
||||
final ViewPB view;
|
||||
@ -30,13 +37,15 @@ class BoardPage extends StatelessWidget {
|
||||
create: (context) =>
|
||||
BoardBloc(view: view)..add(const BoardEvent.initial()),
|
||||
child: BlocBuilder<BoardBloc, BoardState>(
|
||||
buildWhen: (previous, current) =>
|
||||
previous.loadingState != current.loadingState,
|
||||
builder: (context, state) {
|
||||
return state.loadingState.map(
|
||||
loading: (_) =>
|
||||
const Center(child: CircularProgressIndicator.adaptive()),
|
||||
finish: (result) {
|
||||
return result.successOrFail.fold(
|
||||
(_) => BoardContent(),
|
||||
(_) => const BoardContent(),
|
||||
(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(
|
||||
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
|
||||
);
|
||||
|
||||
BoardContent({Key? key}) : super(key: key);
|
||||
@override
|
||||
void initState() {
|
||||
scrollController = ScrollController();
|
||||
scrollManager = AFBoardScrollManager();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
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) {
|
||||
final theme = context.read<AppTheme>();
|
||||
return Container(
|
||||
color: Colors.white,
|
||||
color: theme.surface,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Column(
|
||||
children: [
|
||||
const _ToolbarBlocAdaptor(),
|
||||
Expanded(
|
||||
child: AFBoard(
|
||||
// key: UniqueKey(),
|
||||
scrollController: ScrollController(),
|
||||
dataController: context.read<BoardBloc>().afBoardDataController,
|
||||
scrollManager: scrollManager,
|
||||
scrollController: scrollController,
|
||||
dataController: context.read<BoardBloc>().boardController,
|
||||
headerBuilder: _buildHeader,
|
||||
footBuilder: _buildFooter,
|
||||
cardBuilder: (_, data) => _buildCard(context, data),
|
||||
columnConstraints: const BoxConstraints.tightFor(width: 240),
|
||||
cardBuilder: (_, column, columnItem) => _buildCard(
|
||||
context,
|
||||
column,
|
||||
columnItem,
|
||||
),
|
||||
columnConstraints:
|
||||
const BoxConstraints.tightFor(width: 300),
|
||||
config: AFBoardConfig(
|
||||
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(
|
||||
icon: const Icon(Icons.lightbulb_circle),
|
||||
title: Text(columnData.desc),
|
||||
addIcon: const Icon(Icons.add, size: 20),
|
||||
moreIcon: const Icon(Icons.more_horiz, size: 20),
|
||||
title: Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: FlowyText.medium(
|
||||
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,
|
||||
margin: config.columnItemPadding,
|
||||
margin: config.headerPadding,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFooter(BuildContext context, AFBoardColumnData columnData) {
|
||||
final group = columnData.customData as GroupPB;
|
||||
if (group.isDefault) {
|
||||
return const SizedBox();
|
||||
} else {
|
||||
return AppFlowyColumnFooter(
|
||||
icon: const Icon(Icons.add, size: 20),
|
||||
title: const Text('New'),
|
||||
icon: SizedBox(
|
||||
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,
|
||||
margin: config.columnItemPadding,
|
||||
margin: config.footerPadding,
|
||||
onAddButtonClick: () {
|
||||
context.read<BoardBloc>().add(BoardEvent.createRow(columnData.id));
|
||||
});
|
||||
context.read<BoardBloc>().add(
|
||||
BoardEvent.createBottomRow(columnData.id),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildCard(BuildContext context, AFColumnItem item) {
|
||||
final rowPB = (item as BoardColumnItem).row;
|
||||
Widget _buildCard(
|
||||
BuildContext context,
|
||||
AFBoardColumnData column,
|
||||
AFColumnItem columnItem,
|
||||
) {
|
||||
final boardColumnItem = columnItem as BoardColumnItem;
|
||||
final rowPB = boardColumnItem.row;
|
||||
final rowCache = context.read<BoardBloc>().getRowCache(rowPB.blockId);
|
||||
|
||||
/// Return placeholder widget if the rowCache is null.
|
||||
if (rowCache == null) return SizedBox(key: ObjectKey(item));
|
||||
if (rowCache == null) return SizedBox(key: ObjectKey(columnItem));
|
||||
|
||||
final fieldCache = context.read<BoardBloc>().fieldCache;
|
||||
final gridId = context.read<BoardBloc>().gridId;
|
||||
@ -118,21 +232,25 @@ class BoardContent extends StatelessWidget {
|
||||
);
|
||||
|
||||
final cellBuilder = BoardCellBuilder(cardController);
|
||||
final isEditing = context.read<BoardBloc>().state.editingRow.fold(
|
||||
() => false,
|
||||
(editingRow) => editingRow.id == rowPB.id,
|
||||
bool isEditing = false;
|
||||
context.read<BoardBloc>().state.editingRow.fold(
|
||||
() => null,
|
||||
(editingRow) {
|
||||
isEditing = editingRow.row.id == columnItem.row.id;
|
||||
},
|
||||
);
|
||||
|
||||
return AppFlowyColumnItemCard(
|
||||
key: ObjectKey(item),
|
||||
key: ValueKey(columnItem.id),
|
||||
margin: config.cardPadding,
|
||||
decoration: _makeBoxDecoration(context),
|
||||
child: BoardCard(
|
||||
gridId: gridId,
|
||||
groupId: column.id,
|
||||
fieldId: boardColumnItem.fieldId,
|
||||
isEditing: isEditing,
|
||||
cellBuilder: cellBuilder,
|
||||
dataController: cardController,
|
||||
onEditEditing: (rowId) {
|
||||
context.read<BoardBloc>().add(BoardEvent.endEditRow(rowId));
|
||||
},
|
||||
openCard: (context) => _openCard(
|
||||
gridId,
|
||||
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,
|
||||
GridRowCache rowCache, BuildContext context) {
|
||||
final rowInfo = RowInfo(
|
||||
@ -165,7 +293,27 @@ class BoardContent extends StatelessWidget {
|
||||
cellBuilder: GridCellBuilder(delegate: 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';
|
||||
|
||||
class BoardCheckboxCell extends StatefulWidget {
|
||||
final String groupId;
|
||||
final GridCellControllerBuilder cellControllerBuilder;
|
||||
|
||||
const BoardCheckboxCell({
|
||||
required this.groupId,
|
||||
required this.cellControllerBuilder,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
@ -34,6 +36,8 @@ class _BoardCheckboxCellState extends State<BoardCheckboxCell> {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<BoardCheckboxCellBloc, BoardCheckboxCellState>(
|
||||
buildWhen: (previous, current) =>
|
||||
previous.isSelected != current.isSelected,
|
||||
builder: (context, state) {
|
||||
final icon = state.isSelected
|
||||
? 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/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:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class BoardDateCell extends StatefulWidget {
|
||||
final String groupId;
|
||||
final GridCellControllerBuilder cellControllerBuilder;
|
||||
|
||||
const BoardDateCell({
|
||||
required this.groupId,
|
||||
required this.cellControllerBuilder,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
@ -34,6 +37,7 @@ class _BoardDateCellState extends State<BoardDateCell> {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<BoardDateCellBloc, BoardDateCellState>(
|
||||
buildWhen: (previous, current) => previous.dateStr != current.dateStr,
|
||||
builder: (context, state) {
|
||||
if (state.dateStr.isEmpty) {
|
||||
return const SizedBox();
|
||||
@ -42,7 +46,8 @@ class _BoardDateCellState extends State<BoardDateCell> {
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FlowyText.regular(
|
||||
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';
|
||||
|
||||
class BoardNumberCell extends StatefulWidget {
|
||||
final String groupId;
|
||||
final GridCellControllerBuilder cellControllerBuilder;
|
||||
|
||||
const BoardNumberCell({
|
||||
required this.groupId,
|
||||
required this.cellControllerBuilder,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
@ -34,13 +36,14 @@ class _BoardNumberCellState extends State<BoardNumberCell> {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<BoardNumberCellBloc, BoardNumberCellState>(
|
||||
buildWhen: (previous, current) => previous.content != current.content,
|
||||
builder: (context, state) {
|
||||
if (state.content.isEmpty) {
|
||||
return const SizedBox();
|
||||
} else {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FlowyText.regular(
|
||||
child: FlowyText.medium(
|
||||
state.content,
|
||||
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/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/select_option_editor.dart';
|
||||
import 'package:flutter/material.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;
|
||||
@override
|
||||
final EditableCellNotifier? editableNotifier;
|
||||
|
||||
const BoardSelectOptionCell({
|
||||
required this.groupId,
|
||||
required this.cellControllerBuilder,
|
||||
this.editableNotifier,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@ -33,23 +41,41 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<BoardSelectOptionCellBloc, BoardSelectOptionCellState>(
|
||||
buildWhen: (previous, current) {
|
||||
return previous.selectedOptions != current.selectedOptions;
|
||||
},
|
||||
builder: (context, state) {
|
||||
if (state.selectedOptions
|
||||
.where((element) => element.id == widget.groupId)
|
||||
.isNotEmpty ||
|
||||
state.selectedOptions.isEmpty) {
|
||||
return const SizedBox();
|
||||
} else {
|
||||
final children = state.selectedOptions
|
||||
.map((option) => SelectOptionTag.fromOption(
|
||||
.map(
|
||||
(option) => SelectOptionTag.fromOption(
|
||||
context: context,
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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/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:flutter/material.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;
|
||||
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
|
||||
State<BoardTextCell> createState() => _BoardTextCellState();
|
||||
@ -15,14 +29,48 @@ class BoardTextCell extends StatefulWidget {
|
||||
|
||||
class _BoardTextCellState extends State<BoardTextCell> {
|
||||
late BoardTextCellBloc _cellBloc;
|
||||
late TextEditingController _controller;
|
||||
bool focusWhenInit = false;
|
||||
SingleListenerFocusNode focusNode = SingleListenerFocusNode();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as GridCellController;
|
||||
|
||||
_cellBloc = BoardTextCellBloc(cellController: cellController)
|
||||
..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();
|
||||
}
|
||||
|
||||
@ -30,32 +78,75 @@ class _BoardTextCellState extends State<BoardTextCell> {
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocListener<BoardTextCellBloc, BoardTextCellState>(
|
||||
listener: (context, state) {
|
||||
if (_controller.text != state.content) {
|
||||
_controller.text = state.content;
|
||||
}
|
||||
},
|
||||
child: BlocBuilder<BoardTextCellBloc, BoardTextCellState>(
|
||||
builder: (context, state) {
|
||||
if (state.content.isEmpty) {
|
||||
if (state.content.isEmpty &&
|
||||
state.enableEdit == false &&
|
||||
focusWhenInit == false) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
//
|
||||
Widget child;
|
||||
if (state.enableEdit || focusWhenInit) {
|
||||
child = _buildTextField();
|
||||
} else {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints.loose(
|
||||
const Size(double.infinity, 100),
|
||||
),
|
||||
child: FlowyText.regular(
|
||||
state.content,
|
||||
fontSize: 14,
|
||||
child = _buildText(state);
|
||||
}
|
||||
return Align(alignment: Alignment.centerLeft, child: child);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
Future<void> focusChanged() async {
|
||||
_cellBloc.add(BoardTextCellEvent.updateText(_controller.text));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_cellBloc.close();
|
||||
_controller.dispose();
|
||||
focusNode.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';
|
||||
|
||||
class BoardUrlCell extends StatefulWidget {
|
||||
final String groupId;
|
||||
final GridCellControllerBuilder cellControllerBuilder;
|
||||
|
||||
const BoardUrlCell({
|
||||
required this.groupId,
|
||||
required this.cellControllerBuilder,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
@ -34,6 +36,7 @@ class _BoardUrlCellState extends State<BoardUrlCell> {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<BoardURLCellBloc, BoardURLCellState>(
|
||||
buildWhen: (previous, current) => previous.content != current.content,
|
||||
builder: (context, state) {
|
||||
if (state.content.isEmpty) {
|
||||
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:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'board_cell.dart';
|
||||
import 'card_cell_builder.dart';
|
||||
import 'card_container.dart';
|
||||
|
||||
typedef OnEndEditing = void Function(String rowId);
|
||||
|
||||
class BoardCard extends StatefulWidget {
|
||||
final String gridId;
|
||||
final String groupId;
|
||||
final String fieldId;
|
||||
final bool isEditing;
|
||||
final CardDataController dataController;
|
||||
final BoardCellBuilder cellBuilder;
|
||||
final OnEndEditing onEditEditing;
|
||||
final void Function(BuildContext) openCard;
|
||||
|
||||
const BoardCard({
|
||||
required this.gridId,
|
||||
required this.groupId,
|
||||
required this.fieldId,
|
||||
required this.isEditing,
|
||||
required this.dataController,
|
||||
required this.cellBuilder,
|
||||
required this.onEditEditing,
|
||||
required this.openCard,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
@ -36,13 +37,16 @@ class BoardCard extends StatefulWidget {
|
||||
|
||||
class _BoardCardState extends State<BoardCard> {
|
||||
late BoardCardBloc _cardBloc;
|
||||
late EditableRowNotifier rowNotifier;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
rowNotifier = EditableRowNotifier();
|
||||
_cardBloc = BoardCardBloc(
|
||||
gridId: widget.gridId,
|
||||
fieldId: widget.fieldId,
|
||||
dataController: widget.dataController,
|
||||
);
|
||||
)..add(const BoardCardEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@ -51,16 +55,28 @@ class _BoardCardState extends State<BoardCard> {
|
||||
return BlocProvider.value(
|
||||
value: _cardBloc,
|
||||
child: BlocBuilder<BoardCardBloc, BoardCardState>(
|
||||
buildWhen: (previous, current) {
|
||||
return previous.cells.length != current.cells.length;
|
||||
},
|
||||
builder: (context, state) {
|
||||
return BoardCardContainer(
|
||||
accessoryBuilder: (context) {
|
||||
return [const _CardMoreOption()];
|
||||
return [
|
||||
_CardEditOption(
|
||||
startEditing: () => rowNotifier.becomeFirstResponder(),
|
||||
),
|
||||
const _CardMoreOption(),
|
||||
];
|
||||
},
|
||||
onTap: (context) {
|
||||
widget.openCard(context);
|
||||
},
|
||||
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) {
|
||||
return cellMap.values.map(
|
||||
(cellId) {
|
||||
final child = widget.cellBuilder.buildCell(cellId);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5),
|
||||
List<Widget> _makeCells(
|
||||
BuildContext context,
|
||||
List<GridCellIdentifier> cells,
|
||||
) {
|
||||
final List<Widget> children = [];
|
||||
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,
|
||||
);
|
||||
|
||||
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
|
||||
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
|
||||
@ -96,3 +142,27 @@ class _CardMoreOption extends StatelessWidget with CardAccessory {
|
||||
).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:flutter/material.dart';
|
||||
|
||||
import 'board_cell.dart';
|
||||
import 'board_checkbox_cell.dart';
|
||||
import 'board_date_cell.dart';
|
||||
import 'board_number_cell.dart';
|
||||
@ -19,7 +20,12 @@ class BoardCellBuilder {
|
||||
|
||||
BoardCellBuilder(this.delegate);
|
||||
|
||||
Widget buildCell(GridCellIdentifier cellId) {
|
||||
Widget buildCell(
|
||||
String groupId,
|
||||
GridCellIdentifier cellId,
|
||||
bool isEditing,
|
||||
EditableCellNotifier cellNotifier,
|
||||
) {
|
||||
final cellControllerBuilder = GridCellControllerBuilder(
|
||||
delegate: delegate,
|
||||
cellId: cellId,
|
||||
@ -30,36 +36,46 @@ class BoardCellBuilder {
|
||||
switch (cellId.fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
return BoardCheckboxCell(
|
||||
groupId: groupId,
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.DateTime:
|
||||
return BoardDateCell(
|
||||
groupId: groupId,
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.SingleSelect:
|
||||
return BoardSelectOptionCell(
|
||||
groupId: groupId,
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.MultiSelect:
|
||||
return BoardSelectOptionCell(
|
||||
groupId: groupId,
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
editableNotifier: cellNotifier,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.Number:
|
||||
return BoardNumberCell(
|
||||
groupId: groupId,
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.RichText:
|
||||
return BoardTextCell(
|
||||
groupId: groupId,
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
isFocus: isEditing,
|
||||
editableNotifier: cellNotifier,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.URL:
|
||||
return BoardUrlCell(
|
||||
groupId: groupId,
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
);
|
||||
|
@ -26,8 +26,8 @@ class BoardCardContainer extends StatelessWidget {
|
||||
final accessories = accessoryBuilder!(context);
|
||||
if (accessories.isNotEmpty) {
|
||||
container = _CardEnterRegion(
|
||||
child: container,
|
||||
accessories: accessories,
|
||||
child: container,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -69,25 +69,48 @@ class CardAccessoryContainer extends StatelessWidget {
|
||||
style: HoverStyle(
|
||||
hoverColor: theme.hover,
|
||||
backgroundColor: theme.surface,
|
||||
borderRadius: BorderRadius.zero,
|
||||
),
|
||||
builder: (_, onHover) => Container(
|
||||
width: 26,
|
||||
height: 26,
|
||||
padding: const EdgeInsets.all(3),
|
||||
builder: (_, onHover) => SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: accessory,
|
||||
),
|
||||
);
|
||||
return GestureDetector(
|
||||
child: hover,
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => accessory.onTap(context),
|
||||
child: hover,
|
||||
);
|
||||
}).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 {
|
||||
final Widget child;
|
||||
final List<CardAccessory> accessories;
|
||||
@ -102,8 +125,9 @@ class _CardEnterRegion extends StatelessWidget {
|
||||
builder: (context, onEnter, _) {
|
||||
List<Widget> children = [child];
|
||||
if (onEnter) {
|
||||
children.add(CardAccessoryContainer(accessories: accessories)
|
||||
.positioned(right: 0));
|
||||
children.add(CardAccessoryContainer(
|
||||
accessories: accessories,
|
||||
).positioned(right: 0));
|
||||
}
|
||||
|
||||
return MouseRegion(
|
||||
@ -116,7 +140,7 @@ class _CardEnterRegion extends StatelessWidget {
|
||||
.onEnter = false,
|
||||
child: IntrinsicHeight(
|
||||
child: Stack(
|
||||
alignment: AlignmentDirectional.center,
|
||||
alignment: AlignmentDirectional.topEnd,
|
||||
fit: StackFit.expand,
|
||||
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,
|
||||
outlineColor: Colors.white,
|
||||
borderRadius: Corners.s8Border,
|
||||
onPressed: onRestore,
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.deletePagePrompt_restore.tr(),
|
||||
color: Colors.white,
|
||||
fontSize: 14),
|
||||
onPressed: onRestore),
|
||||
fontSize: 14)),
|
||||
const HSpace(20),
|
||||
BaseStyledButton(
|
||||
minWidth: 220,
|
||||
@ -55,11 +55,11 @@ class DocumentBanner extends StatelessWidget {
|
||||
downColor: theme.main1,
|
||||
outlineColor: Colors.white,
|
||||
borderRadius: Corners.s8Border,
|
||||
onPressed: onDelete,
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.deletePagePrompt_deletePermanent.tr(),
|
||||
color: Colors.white,
|
||||
fontSize: 14),
|
||||
onPressed: onDelete),
|
||||
fontSize: 14)),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -16,7 +16,10 @@ class EditorCheckboxBuilder extends QuillCheckboxBuilder {
|
||||
EditorCheckboxBuilder(this.theme);
|
||||
|
||||
@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(
|
||||
theme: theme,
|
||||
isChecked: isChecked,
|
||||
@ -37,10 +40,10 @@ class FlowyEditorCheckbox extends StatefulWidget {
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_FlowyEditorCheckboxState createState() => _FlowyEditorCheckboxState();
|
||||
FlowyEditorCheckboxState createState() => FlowyEditorCheckboxState();
|
||||
}
|
||||
|
||||
class _FlowyEditorCheckboxState extends State<FlowyEditorCheckbox> {
|
||||
class FlowyEditorCheckboxState extends State<FlowyEditorCheckbox> {
|
||||
late bool isChecked;
|
||||
|
||||
@override
|
||||
@ -51,7 +54,9 @@ class _FlowyEditorCheckboxState extends State<FlowyEditorCheckbox> {
|
||||
|
||||
@override
|
||||
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(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FlowyIconButton(
|
||||
|
@ -28,10 +28,10 @@ class FlowyCheckListButton extends StatefulWidget {
|
||||
final String tooltipText;
|
||||
|
||||
@override
|
||||
_FlowyCheckListButtonState createState() => _FlowyCheckListButtonState();
|
||||
FlowyCheckListButtonState createState() => FlowyCheckListButtonState();
|
||||
}
|
||||
|
||||
class _FlowyCheckListButtonState extends State<FlowyCheckListButton> {
|
||||
class FlowyCheckListButtonState extends State<FlowyCheckListButton> {
|
||||
bool? _isToggled;
|
||||
|
||||
Style get _selectionStyle => widget.controller.getSelectionStyle();
|
||||
|
@ -24,10 +24,10 @@ class FlowyColorButton extends StatefulWidget {
|
||||
final QuillIconTheme? iconTheme;
|
||||
|
||||
@override
|
||||
_FlowyColorButtonState createState() => _FlowyColorButtonState();
|
||||
FlowyColorButtonState createState() => FlowyColorButtonState();
|
||||
}
|
||||
|
||||
class _FlowyColorButtonState extends State<FlowyColorButton> {
|
||||
class FlowyColorButtonState extends State<FlowyColorButton> {
|
||||
late bool _isToggledColor;
|
||||
late bool _isToggledBackground;
|
||||
late bool _isWhite;
|
||||
@ -37,10 +37,14 @@ class _FlowyColorButtonState extends State<FlowyColorButton> {
|
||||
|
||||
void _didChangeEditingValue() {
|
||||
setState(() {
|
||||
_isToggledColor = _getIsToggledColor(widget.controller.getSelectionStyle().attributes);
|
||||
_isToggledBackground = _getIsToggledBackground(widget.controller.getSelectionStyle().attributes);
|
||||
_isWhite = _isToggledColor && _selectionStyle.attributes['color']!.value == '#ffffff';
|
||||
_isWhitebackground = _isToggledBackground && _selectionStyle.attributes['background']!.value == '#ffffff';
|
||||
_isToggledColor =
|
||||
_getIsToggledColor(widget.controller.getSelectionStyle().attributes);
|
||||
_isToggledBackground = _getIsToggledBackground(
|
||||
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();
|
||||
_isToggledColor = _getIsToggledColor(_selectionStyle.attributes);
|
||||
_isToggledBackground = _getIsToggledBackground(_selectionStyle.attributes);
|
||||
_isWhite = _isToggledColor && _selectionStyle.attributes['color']!.value == '#ffffff';
|
||||
_isWhitebackground = _isToggledBackground && _selectionStyle.attributes['background']!.value == '#ffffff';
|
||||
_isWhite = _isToggledColor &&
|
||||
_selectionStyle.attributes['color']!.value == '#ffffff';
|
||||
_isWhitebackground = _isToggledBackground &&
|
||||
_selectionStyle.attributes['background']!.value == '#ffffff';
|
||||
widget.controller.addListener(_didChangeEditingValue);
|
||||
}
|
||||
|
||||
@ -69,9 +75,12 @@ class _FlowyColorButtonState extends State<FlowyColorButton> {
|
||||
oldWidget.controller.removeListener(_didChangeEditingValue);
|
||||
widget.controller.addListener(_didChangeEditingValue);
|
||||
_isToggledColor = _getIsToggledColor(_selectionStyle.attributes);
|
||||
_isToggledBackground = _getIsToggledBackground(_selectionStyle.attributes);
|
||||
_isWhite = _isToggledColor && _selectionStyle.attributes['color']!.value == '#ffffff';
|
||||
_isWhitebackground = _isToggledBackground && _selectionStyle.attributes['background']!.value == '#ffffff';
|
||||
_isToggledBackground =
|
||||
_getIsToggledBackground(_selectionStyle.attributes);
|
||||
_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
|
||||
? stringToColor('#ffffff')
|
||||
: (widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor);
|
||||
final fillColorBackground = _isToggledBackground && widget.background && _isWhitebackground
|
||||
final fillColorBackground =
|
||||
_isToggledBackground && widget.background && _isWhitebackground
|
||||
? stringToColor('#ffffff')
|
||||
: (widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor);
|
||||
|
||||
@ -99,7 +109,8 @@ class _FlowyColorButtonState extends State<FlowyColorButton> {
|
||||
highlightElevation: 0,
|
||||
hoverElevation: 0,
|
||||
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,
|
||||
onPressed: _showColorPicker,
|
||||
),
|
||||
@ -112,13 +123,16 @@ class _FlowyColorButtonState extends State<FlowyColorButton> {
|
||||
hex = hex.substring(2);
|
||||
}
|
||||
hex = '#$hex';
|
||||
widget.controller.formatSelection(widget.background ? BackgroundAttribute(hex) : ColorAttribute(hex));
|
||||
widget.controller.formatSelection(
|
||||
widget.background ? BackgroundAttribute(hex) : ColorAttribute(hex));
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
void _showColorPicker() {
|
||||
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;
|
||||
if (values.isNotEmpty) {
|
||||
assert(values.length == 1);
|
||||
@ -160,7 +174,9 @@ class FlowyColorPicker extends StatefulWidget {
|
||||
];
|
||||
final Function(Color?) onColorChanged;
|
||||
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
|
||||
State<FlowyColorPicker> createState() => _FlowyColorPickerState();
|
||||
@ -178,8 +194,10 @@ class _FlowyColorPickerState extends State<FlowyColorPicker> {
|
||||
const double crossAxisSpacing = 10;
|
||||
final numberOfRows = (widget.colors.length / crossAxisCount).ceil();
|
||||
|
||||
const perRowHeight = ((width - ((crossAxisCount - 1) * mainAxisSpacing)) / crossAxisCount);
|
||||
final totalHeight = numberOfRows * perRowHeight + numberOfRows * crossAxisSpacing;
|
||||
const perRowHeight =
|
||||
((width - ((crossAxisCount - 1) * mainAxisSpacing)) / crossAxisCount);
|
||||
final totalHeight =
|
||||
numberOfRows * perRowHeight + numberOfRows * crossAxisSpacing;
|
||||
|
||||
return Container(
|
||||
constraints: BoxConstraints.tightFor(width: width, height: totalHeight),
|
||||
@ -198,7 +216,8 @@ class _FlowyColorPickerState extends State<FlowyColorPicker> {
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
if (widget.colors.length > index) {
|
||||
final isSelected = widget.colors[index] == widget.initialColor;
|
||||
final isSelected =
|
||||
widget.colors[index] == widget.initialColor;
|
||||
return ColorItem(
|
||||
color: Color(widget.colors[index]),
|
||||
onPressed: widget.onColorChanged,
|
||||
@ -242,7 +261,8 @@ class ColorItem extends StatelessWidget {
|
||||
);
|
||||
} else {
|
||||
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)),
|
||||
onPressed: () {
|
||||
if (isSelected) {
|
||||
|
@ -16,10 +16,10 @@ class FlowyHeaderStyleButton extends StatefulWidget {
|
||||
final double iconSize;
|
||||
|
||||
@override
|
||||
_FlowyHeaderStyleButtonState createState() => _FlowyHeaderStyleButtonState();
|
||||
FlowyHeaderStyleButtonState createState() => FlowyHeaderStyleButtonState();
|
||||
}
|
||||
|
||||
class _FlowyHeaderStyleButtonState extends State<FlowyHeaderStyleButton> {
|
||||
class FlowyHeaderStyleButtonState extends State<FlowyHeaderStyleButton> {
|
||||
Attribute? _value;
|
||||
|
||||
Style get _selectionStyle => widget.controller.getSelectionStyle();
|
||||
@ -28,22 +28,27 @@ class _FlowyHeaderStyleButtonState extends State<FlowyHeaderStyleButton> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
setState(() {
|
||||
_value = _selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
|
||||
_value =
|
||||
_selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
|
||||
});
|
||||
widget.controller.addListener(_didChangeEditingValue);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final _valueToText = <Attribute, String>{
|
||||
final valueToText = <Attribute, String>{
|
||||
Attribute.h1: 'H1',
|
||||
Attribute.h2: 'H2',
|
||||
Attribute.h3: 'H3',
|
||||
};
|
||||
|
||||
final _valueAttribute = <Attribute>[Attribute.h1, Attribute.h2, Attribute.h3];
|
||||
final _valueString = <String>['H1', 'H2', 'H3'];
|
||||
final _attributeImageName = <String>['editor/H1', 'editor/H2', 'editor/H3'];
|
||||
final valueAttribute = <Attribute>[
|
||||
Attribute.h1,
|
||||
Attribute.h2,
|
||||
Attribute.h3
|
||||
];
|
||||
final valueString = <String>['H1', 'H2', 'H3'];
|
||||
final attributeImageName = <String>['editor/H1', 'editor/H2', 'editor/H3'];
|
||||
|
||||
return Row(
|
||||
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');
|
||||
|
||||
final headerTitle = "${LocaleKeys.toolbar_header.tr()} ${index + 1}";
|
||||
final _isToggled = _valueToText[_value] == _valueString[index];
|
||||
final isToggled = valueToText[_value] == valueString[index];
|
||||
return ToolbarIconButton(
|
||||
onPressed: () {
|
||||
if (_isToggled) {
|
||||
if (isToggled) {
|
||||
widget.controller.formatSelection(Attribute.header);
|
||||
} else {
|
||||
widget.controller.formatSelection(_valueAttribute[index]);
|
||||
widget.controller.formatSelection(valueAttribute[index]);
|
||||
}
|
||||
},
|
||||
width: widget.iconSize * kIconButtonFactor,
|
||||
iconName: _attributeImageName[index],
|
||||
isToggled: _isToggled,
|
||||
iconName: attributeImageName[index],
|
||||
isToggled: isToggled,
|
||||
tooltipText: headerTitle,
|
||||
);
|
||||
}),
|
||||
@ -72,7 +77,8 @@ class _FlowyHeaderStyleButtonState extends State<FlowyHeaderStyleButton> {
|
||||
|
||||
void _didChangeEditingValue() {
|
||||
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) {
|
||||
oldWidget.controller.removeListener(_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;
|
||||
|
||||
@override
|
||||
_FlowyLinkStyleButtonState createState() => _FlowyLinkStyleButtonState();
|
||||
FlowyLinkStyleButtonState createState() => FlowyLinkStyleButtonState();
|
||||
}
|
||||
|
||||
class _FlowyLinkStyleButtonState extends State<FlowyLinkStyleButton> {
|
||||
class FlowyLinkStyleButtonState extends State<FlowyLinkStyleButton> {
|
||||
void _didChangeSelection() {
|
||||
setState(() {});
|
||||
}
|
||||
@ -75,7 +75,9 @@ class _FlowyLinkStyleButtonState extends State<FlowyLinkStyleButton> {
|
||||
|
||||
void _openLinkDialog(BuildContext context) {
|
||||
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 = "";
|
||||
if (values.isNotEmpty) {
|
||||
assert(values.length == 1);
|
||||
|
@ -21,10 +21,10 @@ class FlowyToggleStyleButton extends StatefulWidget {
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_ToggleStyleButtonState createState() => _ToggleStyleButtonState();
|
||||
ToggleStyleButtonState createState() => ToggleStyleButtonState();
|
||||
}
|
||||
|
||||
class _ToggleStyleButtonState extends State<FlowyToggleStyleButton> {
|
||||
class ToggleStyleButtonState extends State<FlowyToggleStyleButton> {
|
||||
bool? _isToggled;
|
||||
Style get _selectionStyle => widget.controller.getSelectionStyle();
|
||||
@override
|
||||
@ -77,6 +77,8 @@ class _ToggleStyleButtonState extends State<FlowyToggleStyleButton> {
|
||||
}
|
||||
|
||||
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(
|
||||
color: Theme.of(context).canvasColor,
|
||||
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;
|
||||
|
||||
@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();
|
||||
bool _showLeftArrow = false;
|
||||
bool _showRightArrow = false;
|
||||
@ -196,7 +198,8 @@ class _ToolbarButtonListState extends State<ToolbarButtonList> with WidgetsBindi
|
||||
return LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
List<Widget> children = [];
|
||||
double width = (widget.buttons.length + 2) * defaultIconSize * kIconButtonFactor;
|
||||
double width =
|
||||
(widget.buttons.length + 2) * defaultIconSize * kIconButtonFactor;
|
||||
final isFit = constraints.maxWidth > width;
|
||||
if (!isFit) {
|
||||
children.add(_buildLeftArrow());
|
||||
@ -233,8 +236,10 @@ class _ToolbarButtonListState extends State<ToolbarButtonList> with WidgetsBindi
|
||||
void _handleScroll() {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_showLeftArrow = _controller.position.minScrollExtent != _controller.position.pixels;
|
||||
_showRightArrow = _controller.position.maxScrollExtent != _controller.position.pixels;
|
||||
_showLeftArrow =
|
||||
_controller.position.minScrollExtent != _controller.position.pixels;
|
||||
_showRightArrow =
|
||||
_controller.position.maxScrollExtent != _controller.position.pixels;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -11,13 +11,15 @@ typedef UpdateFieldNotifiedValue = Either<Unit, FlowyError>;
|
||||
class CellListener {
|
||||
final String rowId;
|
||||
final String fieldId;
|
||||
PublishNotifier<UpdateFieldNotifiedValue>? _updateCellNotifier = PublishNotifier();
|
||||
PublishNotifier<UpdateFieldNotifiedValue>? _updateCellNotifier =
|
||||
PublishNotifier();
|
||||
GridNotificationListener? _listener;
|
||||
CellListener({required this.rowId, required this.fieldId});
|
||||
|
||||
void start({required void Function(UpdateFieldNotifiedValue) 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) {
|
||||
|
@ -33,10 +33,17 @@ class GridCellCache {
|
||||
required this.gridId,
|
||||
});
|
||||
|
||||
void remove(String fieldId) {
|
||||
void removeCellWithFieldId(String 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) {
|
||||
var map = _cellDataByFieldId[key.fieldId];
|
||||
if (map == null) {
|
||||
|
@ -24,7 +24,8 @@ class GridCellDataLoader<T> {
|
||||
Future<T?> loadData() {
|
||||
final fut = service.getCell(cellId: cellId);
|
||||
return fut.then(
|
||||
(result) => result.fold((GridCellPB cell) {
|
||||
(result) => result.fold(
|
||||
(GridCellPB cell) {
|
||||
try {
|
||||
return parser.parserData(cell.data);
|
||||
} catch (e, s) {
|
||||
@ -32,10 +33,12 @@ class GridCellDataLoader<T> {
|
||||
Log.error('Stack trace \n $s');
|
||||
return null;
|
||||
}
|
||||
}, (err) {
|
||||
},
|
||||
(err) {
|
||||
Log.error(err);
|
||||
return null;
|
||||
}),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -58,7 +61,8 @@ class DateCellDataParser implements IGridCellDataParser<DateCellDataPB> {
|
||||
}
|
||||
}
|
||||
|
||||
class SelectOptionCellDataParser implements IGridCellDataParser<SelectOptionCellDataPB> {
|
||||
class SelectOptionCellDataParser
|
||||
implements IGridCellDataParser<SelectOptionCellDataPB> {
|
||||
@override
|
||||
SelectOptionCellDataPB? parserData(List<int> data) {
|
||||
if (data.isEmpty) {
|
||||
|
@ -71,6 +71,6 @@ class GridCellIdentifier with _$GridCellIdentifier {
|
||||
FieldType get fieldType => field.fieldType;
|
||||
|
||||
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
|
||||
_cellListener?.start(onCellChanged: (result) {
|
||||
result.fold(
|
||||
(_) => _loadData(),
|
||||
(_) {
|
||||
_cellsCache.remove(_cacheKey);
|
||||
_loadData();
|
||||
},
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
@ -279,8 +282,8 @@ class IGridCellController<T, D> extends Equatable {
|
||||
_loadDataOperation?.cancel();
|
||||
_loadDataOperation = Timer(const Duration(milliseconds: 10), () {
|
||||
_cellDataLoader.loadData().then((data) {
|
||||
_cellDataNotifier?.value = 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 msg = LocaleKeys.grid_field_invalidTimeFormat.tr() + ". ";
|
||||
String msg = "${LocaleKeys.grid_field_invalidTimeFormat.tr()}. ";
|
||||
switch (state.dateTypeOptionPB.timeFormat) {
|
||||
case TimeFormat.TwelveHour:
|
||||
msg = msg + "e.g. 01: 00 AM";
|
||||
msg = "${msg}e.g. 01: 00 AM";
|
||||
break;
|
||||
case TimeFormat.TwentyFourHour:
|
||||
msg = msg + "e.g. 13: 00";
|
||||
msg = "${msg}e.g. 13: 00";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -79,7 +79,7 @@ class DateCellState with _$DateCellState {
|
||||
String _dateStrFromCellData(DateCellDataPB? cellData) {
|
||||
String dateStr = "";
|
||||
if (cellData != null) {
|
||||
dateStr = cellData.date + " " + cellData.time;
|
||||
dateStr = "${cellData.date} ${cellData.time}";
|
||||
}
|
||||
return dateStr;
|
||||
}
|
||||
|
@ -1,12 +1,14 @@
|
||||
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:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
|
||||
import 'select_option_service.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
part 'select_option_editor_bloc.freezed.dart';
|
||||
|
||||
|
@ -46,7 +46,8 @@ class GridDataController {
|
||||
|
||||
GridDataController({required ViewPB view})
|
||||
: gridId = view.id,
|
||||
_blocks = LinkedHashMap.new(),
|
||||
// ignore: prefer_collection_literals
|
||||
_blocks = LinkedHashMap(),
|
||||
_gridFFIService = GridFFIService(gridId: view.id),
|
||||
fieldCache = GridFieldCache(gridId: view.id);
|
||||
|
||||
|
@ -27,10 +27,18 @@ class GridFFIService {
|
||||
return GridEventCreateTableRow(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<RowPB, FlowyError>> createBoardCard(String groupId) {
|
||||
Future<Either<RowPB, FlowyError>> createBoardCard(
|
||||
String groupId,
|
||||
String? startRowId,
|
||||
) {
|
||||
CreateBoardCardPayloadPB payload = CreateBoardCardPayloadPB.create()
|
||||
..gridId = gridId
|
||||
..groupId = groupId;
|
||||
|
||||
if (startRowId != null) {
|
||||
payload.startRowId = startRowId;
|
||||
}
|
||||
|
||||
return GridEventCreateBoardCard(payload).send();
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,8 @@ class GridRowCache {
|
||||
//
|
||||
notifier.onRowFieldsChanged(() => _rowChangeReasonNotifier
|
||||
.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();
|
||||
}
|
||||
|
||||
@ -209,7 +210,8 @@ class GridRowCache {
|
||||
}
|
||||
|
||||
GridCellMap _makeGridCells(String rowId, RowPB? row) {
|
||||
var cellDataMap = GridCellMap.new();
|
||||
// ignore: prefer_collection_literals
|
||||
var cellDataMap = GridCellMap();
|
||||
for (final field in _fieldNotifier.fields) {
|
||||
if (field.visibility) {
|
||||
cellDataMap[field.id] = GridCellIdentifier(
|
||||
|
@ -190,12 +190,12 @@ class CellAccessoryContainer extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
return GestureDetector(
|
||||
child: hover,
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => accessory.onTap(),
|
||||
child: hover,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
return Wrap(children: children, spacing: 6);
|
||||
return Wrap(spacing: 6, children: children);
|
||||
}
|
||||
}
|
||||
|
@ -44,8 +44,8 @@ class CellContainer extends StatelessWidget {
|
||||
|
||||
if (accessories.isNotEmpty) {
|
||||
container = _GridCellEnterRegion(
|
||||
child: container,
|
||||
accessories: accessories,
|
||||
child: container,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -297,9 +297,8 @@ class _DateTypeOptionButton extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
final title = LocaleKeys.grid_field_dateFormat.tr() +
|
||||
" &" +
|
||||
LocaleKeys.grid_field_timeFormat.tr();
|
||||
final title =
|
||||
"${LocaleKeys.grid_field_dateFormat.tr()} &${LocaleKeys.grid_field_timeFormat.tr()}";
|
||||
return BlocSelector<DateCalBloc, DateCalState, DateTypeOptionPB>(
|
||||
selector: (state) => state.dateTypeOptionPB,
|
||||
builder: (context, dateTypeOptionPB) {
|
||||
@ -406,8 +405,8 @@ class _CalDateTimeSettingState extends State<_CalDateTimeSetting> {
|
||||
overlayIdentifier = child.toString();
|
||||
FlowyOverlay.of(context).insertWithAnchor(
|
||||
widget: OverlayContainer(
|
||||
child: child,
|
||||
constraints: BoxConstraints.loose(const Size(460, 440)),
|
||||
child: child,
|
||||
),
|
||||
identifier: overlayIdentifier!,
|
||||
anchorContext: context,
|
||||
|
@ -91,8 +91,11 @@ class SelectOptionTag extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return ChoiceChip(
|
||||
pressElevation: 1,
|
||||
label:
|
||||
FlowyText.medium(name, fontSize: 12, overflow: TextOverflow.ellipsis),
|
||||
label: FlowyText.medium(
|
||||
name,
|
||||
fontSize: 12,
|
||||
overflow: TextOverflow.clip,
|
||||
),
|
||||
selectedColor: color,
|
||||
backgroundColor: color,
|
||||
labelPadding: const EdgeInsets.symmetric(horizontal: 6),
|
||||
|
@ -178,14 +178,14 @@ class _SelectOptionWrapState extends State<SelectOptionWrap> {
|
||||
child = Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Wrap(
|
||||
spacing: 4,
|
||||
runSpacing: 2,
|
||||
children: widget.selectOptions
|
||||
.map((option) => SelectOptionTag.fromOption(
|
||||
context: context,
|
||||
option: option,
|
||||
))
|
||||
.toList(),
|
||||
spacing: 4,
|
||||
runSpacing: 2,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -75,8 +75,8 @@ class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
|
||||
//
|
||||
FlowyOverlay.of(context).insertWithAnchor(
|
||||
widget: OverlayContainer(
|
||||
child: SizedBox(width: _editorPannelWidth, child: editor),
|
||||
constraints: BoxConstraints.loose(const Size(_editorPannelWidth, 300)),
|
||||
child: SizedBox(width: _editorPannelWidth, child: editor),
|
||||
),
|
||||
identifier: SelectOptionCellEditor.identifier(),
|
||||
anchorContext: context,
|
||||
|
@ -108,7 +108,7 @@ class SelectOptionTextField extends StatelessWidget {
|
||||
child: SingleChildScrollView(
|
||||
controller: sc,
|
||||
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(
|
||||
widget: OverlayContainer(
|
||||
constraints: BoxConstraints.loose(const Size(300, 160)),
|
||||
child: SizedBox(
|
||||
width: 200,
|
||||
child: Padding(padding: const EdgeInsets.all(6), child: editor),
|
||||
),
|
||||
constraints: BoxConstraints.loose(const Size(300, 160)),
|
||||
),
|
||||
identifier: URLCellEditor.identifier(),
|
||||
anchorContext: context,
|
||||
|
@ -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/type_option/type_option_context.dart';
|
||||
import 'package:app_flowy/startup/startup.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:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
@ -178,7 +180,10 @@ class CreateFieldButton extends StatelessWidget {
|
||||
triggerActions: PopoverTriggerActionFlags.click,
|
||||
direction: PopoverDirection.bottomWithRightAligned,
|
||||
child: FlowyButton(
|
||||
text: const FlowyText.medium('New column', fontSize: 12),
|
||||
text: FlowyText.medium(
|
||||
LocaleKeys.grid_field_newColumn.tr(),
|
||||
fontSize: 12,
|
||||
),
|
||||
hoverColor: theme.shader6,
|
||||
onTap: () {},
|
||||
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 {
|
||||
final _SelectNumberFormatCallback onSelected;
|
||||
final SelectNumberFormatCallback onSelected;
|
||||
final NumberFormat selectedFormat;
|
||||
const NumberFormatList(
|
||||
{required this.selectedFormat, required this.onSelected, Key? key})
|
||||
|
@ -14,6 +14,8 @@ import '../cell/cell_accessory.dart';
|
||||
import '../cell/cell_container.dart';
|
||||
import '../cell/prelude.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 {
|
||||
final RowInfo rowInfo;
|
||||
@ -122,10 +124,13 @@ class _InsertRowButton extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
return FlowyIconButton(
|
||||
tooltipText: LocaleKeys.tooltip_addNewRow.tr(),
|
||||
hoverColor: theme.hover,
|
||||
width: 20,
|
||||
height: 30,
|
||||
onPressed: () => context.read<RowBloc>().add(const RowEvent.createRow()),
|
||||
onPressed: () => context.read<RowBloc>().add(
|
||||
const RowEvent.createRow(),
|
||||
),
|
||||
iconPadding: const EdgeInsets.all(3),
|
||||
icon: svgWidget("home/add"),
|
||||
);
|
||||
@ -139,6 +144,7 @@ class _DeleteRowButton extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
return FlowyIconButton(
|
||||
tooltipText: LocaleKeys.tooltip_openMenu.tr(),
|
||||
hoverColor: theme.hover,
|
||||
width: 20,
|
||||
height: 30,
|
||||
@ -184,7 +190,6 @@ class RowContent extends StatelessWidget {
|
||||
|
||||
return CellContainer(
|
||||
width: cellId.field.width.toDouble(),
|
||||
child: child,
|
||||
rowStateNotifier:
|
||||
Provider.of<RegionStateNotifier>(context, listen: false),
|
||||
accessoryBuilder: (buildContext) {
|
||||
@ -202,6 +207,7 @@ class RowContent extends StatelessWidget {
|
||||
}
|
||||
return accessories;
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
).toList();
|
||||
|
@ -59,8 +59,8 @@ class GridRowActionSheet extends StatelessWidget {
|
||||
}) {
|
||||
FlowyOverlay.of(overlayContext).insertWithAnchor(
|
||||
widget: OverlayContainer(
|
||||
child: this,
|
||||
constraints: BoxConstraints.loose(const Size(140, 200)),
|
||||
child: this,
|
||||
),
|
||||
identifier: GridRowActionSheet.identifier(),
|
||||
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/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/icon_button.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:easy_localization/easy_localization.dart';
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
@ -61,7 +63,12 @@ class _RowDetailPageState extends State<RowDetailPage> {
|
||||
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 {
|
||||
final String viewId;
|
||||
final GridCellBuilder cellBuilder;
|
||||
final ScrollController _scrollController;
|
||||
_PropertyList({
|
||||
required this.viewId,
|
||||
required this.cellBuilder,
|
||||
Key? key,
|
||||
}) : _scrollController = ScrollController(),
|
||||
@ -101,7 +110,10 @@ class _PropertyList extends StatelessWidget {
|
||||
return BlocBuilder<RowDetailBloc, RowDetailState>(
|
||||
buildWhen: (previous, current) => previous.gridCells != current.gridCells,
|
||||
builder: (context, state) {
|
||||
return ScrollbarListStack(
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ScrollbarListStack(
|
||||
axis: Axis.vertical,
|
||||
controller: _scrollController,
|
||||
barSize: GridSize.scrollBarSize,
|
||||
@ -118,6 +130,46 @@ class _PropertyList extends StatelessWidget {
|
||||
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,
|
||||
onTap: () => cell.beginFocus.notify(),
|
||||
child: AccessoryHover(
|
||||
child: cell,
|
||||
contentPadding:
|
||||
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:appflowy_popover/popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
|
@ -91,12 +91,12 @@ class _TrashPageState extends State<TrashPage> {
|
||||
builder: (context, state) {
|
||||
return SizedBox.expand(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
_renderTopBar(context, theme, state),
|
||||
const VSpace(32),
|
||||
_renderTrashList(context, state),
|
||||
],
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
).padding(horizontal: horizontalPadding, vertical: 48),
|
||||
);
|
||||
},
|
||||
|
@ -20,11 +20,10 @@ class InitAppWidgetTask extends LaunchTask {
|
||||
final setting = await UserSettingsService().getAppearanceSettings();
|
||||
final settingModel = AppearanceSettingModel(setting);
|
||||
final app = ApplicationWidget(
|
||||
child: widget,
|
||||
settingModel: settingModel,
|
||||
child: widget,
|
||||
);
|
||||
BlocOverrides.runZoned(
|
||||
() {
|
||||
Bloc.observer = ApplicationBlocObserver();
|
||||
runApp(
|
||||
EasyLocalization(
|
||||
supportedLocales: const [
|
||||
@ -51,9 +50,6 @@ class InitAppWidgetTask extends LaunchTask {
|
||||
child: app,
|
||||
),
|
||||
);
|
||||
},
|
||||
blocObserver: ApplicationBlocObserver(),
|
||||
);
|
||||
|
||||
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(
|
||||
context,
|
||||
PageRoutes.fade(() => HomeScreen(profile, workspaceSetting), RouteDurations.slow.inMilliseconds * .001),
|
||||
PageRoutes.fade(() => HomeScreen(profile, workspaceSetting),
|
||||
RouteDurations.slow.inMilliseconds * .001),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SplashRoute {
|
||||
Future<void> pushWelcomeScreen(BuildContext context, UserProfilePB userProfile) async {
|
||||
Future<void> pushWelcomeScreen(
|
||||
BuildContext context, UserProfilePB userProfile) async {
|
||||
final screen = WelcomeScreen(userProfile: userProfile);
|
||||
final workspaceId = await Navigator.of(context).push(
|
||||
PageRoutes.fade(
|
||||
@ -46,20 +49,24 @@ class SplashRoute {
|
||||
),
|
||||
);
|
||||
|
||||
// ignore: use_build_context_synchronously
|
||||
pushHomeScreen(context, userProfile, workspaceId);
|
||||
}
|
||||
|
||||
void pushHomeScreen(BuildContext context, UserProfilePB userProfile, CurrentWorkspaceSettingPB workspaceSetting) {
|
||||
void pushHomeScreen(BuildContext context, UserProfilePB userProfile,
|
||||
CurrentWorkspaceSettingPB workspaceSetting) {
|
||||
Navigator.push(
|
||||
context,
|
||||
PageRoutes.fade(() => HomeScreen(userProfile, workspaceSetting), RouteDurations.slow.inMilliseconds * .001),
|
||||
PageRoutes.fade(() => HomeScreen(userProfile, workspaceSetting),
|
||||
RouteDurations.slow.inMilliseconds * .001),
|
||||
);
|
||||
}
|
||||
|
||||
void pushSignInScreen(BuildContext context) {
|
||||
Navigator.push(
|
||||
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) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(LocaleKeys.signIn_dontHaveAnAccount.tr(), style: TextStyle(color: theme.shader3, fontSize: 12)),
|
||||
TextButton(
|
||||
@ -107,7 +108,6 @@ class SignUpPrompt extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
],
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -86,6 +86,7 @@ class SignUpPrompt extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
LocaleKeys.signUp_alreadyHaveAnAccount.tr(),
|
||||
@ -97,7 +98,6 @@ class SignUpPrompt extends StatelessWidget {
|
||||
child: Text(LocaleKeys.signIn_buttonText.tr(), style: TextStyle(color: theme.main1)),
|
||||
),
|
||||
],
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -399,8 +399,8 @@ class AutolinkExtensionSyntax extends InlineSyntax {
|
||||
}
|
||||
}
|
||||
|
||||
class _DelimiterRun {
|
||||
_DelimiterRun._(
|
||||
class DelimiterRun {
|
||||
DelimiterRun._(
|
||||
{this.char,
|
||||
this.length,
|
||||
this.isLeftFlanking,
|
||||
@ -420,8 +420,7 @@ class _DelimiterRun {
|
||||
final bool? isFollowedByPunctuation;
|
||||
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static _DelimiterRun? tryParse(
|
||||
InlineParser parser, int runStart, int runEnd) {
|
||||
static DelimiterRun? tryParse(InlineParser parser, int runStart, int runEnd) {
|
||||
bool leftFlanking,
|
||||
rightFlanking,
|
||||
precededByPunctuation,
|
||||
@ -466,7 +465,7 @@ class _DelimiterRun {
|
||||
return null;
|
||||
}
|
||||
|
||||
return _DelimiterRun._(
|
||||
return DelimiterRun._(
|
||||
char: parser.charAt(runStart),
|
||||
length: runEnd - runStart + 1,
|
||||
isLeftFlanking: leftFlanking,
|
||||
@ -516,7 +515,7 @@ class TagSyntax extends InlineSyntax {
|
||||
return true;
|
||||
}
|
||||
|
||||
final delimiterRun = _DelimiterRun.tryParse(parser, matchStart, matchEnd);
|
||||
final delimiterRun = DelimiterRun.tryParse(parser, matchStart, matchEnd);
|
||||
if (delimiterRun != null && delimiterRun.canOpen) {
|
||||
parser.openTag(TagState(parser.pos, matchEnd + 1, this, delimiterRun));
|
||||
return true;
|
||||
@ -531,7 +530,7 @@ class TagSyntax extends InlineSyntax {
|
||||
final matchStart = parser.pos;
|
||||
final matchEnd = parser.pos + runLength - 1;
|
||||
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) {
|
||||
parser.addNode(Element('em', state.children));
|
||||
@ -579,7 +578,7 @@ class StrikethroughSyntax extends TagSyntax {
|
||||
final runLength = match.group(0)!.length;
|
||||
final matchStart = parser.pos;
|
||||
final matchEnd = parser.pos + runLength - 1;
|
||||
final delimiterRun = _DelimiterRun.tryParse(parser, matchStart, matchEnd)!;
|
||||
final delimiterRun = DelimiterRun.tryParse(parser, matchStart, matchEnd)!;
|
||||
if (!delimiterRun.isRightFlanking!) {
|
||||
return false;
|
||||
}
|
||||
@ -1170,7 +1169,7 @@ class TagState {
|
||||
/// The children of this node. Will be `null` for text nodes.
|
||||
final List<Node> children;
|
||||
|
||||
final _DelimiterRun? openingDelimiterRun;
|
||||
final DelimiterRun? openingDelimiterRun;
|
||||
|
||||
/// Attempts to close this tag by matching the current text against its end
|
||||
/// pattern.
|
||||
@ -1193,7 +1192,7 @@ class TagState {
|
||||
final closingMatchStart = parser.pos;
|
||||
final closingMatchEnd = parser.pos + runLength - 1;
|
||||
final closingDelimiterRun =
|
||||
_DelimiterRun.tryParse(parser, closingMatchStart, closingMatchEnd);
|
||||
DelimiterRun.tryParse(parser, closingMatchStart, closingMatchEnd);
|
||||
if (closingDelimiterRun != null && closingDelimiterRun.canClose) {
|
||||
// Emphasis rules #9 and #10:
|
||||
final oneRunOpensAndCloses =
|
||||
|
@ -1,5 +1,7 @@
|
||||
import 'package:app_flowy/startup/plugin/plugin.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/presentation/widgets/edit_panel/panel_animation.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(
|
||||
body: BlocListener<HomeBloc, HomeState>(
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
Widget _buildEditPanel(
|
||||
{required HomeState homeState,
|
||||
required BuildContext context,
|
||||
|
@ -58,10 +58,10 @@ class FadingIndexedStack extends StatefulWidget {
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_FadingIndexedStackState createState() => _FadingIndexedStackState();
|
||||
FadingIndexedStackState createState() => FadingIndexedStackState();
|
||||
}
|
||||
|
||||
class _FadingIndexedStackState extends State<FadingIndexedStack> {
|
||||
class FadingIndexedStackState extends State<FadingIndexedStack> {
|
||||
double _targetOpacity = 1;
|
||||
|
||||
@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