Compare commits
99
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a14ba8047 | ||
|
|
5daf9d23f5 | ||
|
|
b087a9aeb3 | ||
|
|
107e3cea4f | ||
|
|
ba965caa8f | ||
|
|
2af93a9bcb | ||
|
|
f8962edfd7 | ||
|
|
b236afd0c3 | ||
|
|
8944edf75f | ||
|
|
6b05be2362 | ||
|
|
90146148b6 | ||
|
|
63464cbf2e | ||
|
|
1fadb15351 | ||
|
|
8732c3c28b | ||
|
|
f7233f6949 | ||
|
|
8c7ed808a7 | ||
|
|
b38fc43300 | ||
|
|
f4170755fa | ||
|
|
79585d0835 | ||
|
|
835563f81b | ||
|
|
37bc5b3fbf | ||
|
|
682bf19838 | ||
|
|
f4ca3ef782 | ||
|
|
f826d05f03 | ||
|
|
01f2b15f70 | ||
|
|
2d6856a23d | ||
|
|
2286a1c726 | ||
|
|
ef90c35175 | ||
|
|
af12beb1c3 | ||
|
|
0d68c42aab | ||
|
|
0cffe42d80 | ||
|
|
abd08e5e53 | ||
|
|
e250f780a4 | ||
|
|
980bebf86a | ||
|
|
26f8397624 | ||
|
|
d67e904f56 | ||
|
|
f36ca3777b | ||
|
|
74233d2606 | ||
|
|
faea0fa532 | ||
|
|
3454af1caa | ||
|
|
8d50ad2936 | ||
|
|
daedf95c95 | ||
|
|
95bfda9dc6 | ||
|
|
93664167e0 | ||
|
|
dfe3b0a929 | ||
|
|
e2028ac5a0 | ||
|
|
c9dc24a13c | ||
|
|
609557c357 | ||
|
|
93af6e69a1 | ||
|
|
b75947b630 | ||
|
|
b60d25a0e3 | ||
|
|
537fa52e77 | ||
|
|
d861ce6227 | ||
|
|
c3e5aa29fc | ||
|
|
4ca3ba8e08 | ||
|
|
f5cc6521fb | ||
|
|
cea1c17b76 | ||
|
|
2abb396467 | ||
|
|
236b5bfe90 | ||
|
|
fa3bc97698 | ||
|
|
c0796e8ae5 | ||
|
|
acd75befbc | ||
|
|
746f0817bc | ||
|
|
c0b667b4ea | ||
|
|
ef83688907 | ||
|
|
c06a0346d8 | ||
|
|
4a0c48f419 | ||
|
|
7802c75d7c | ||
|
|
f1831d07af | ||
|
|
6636731487 | ||
|
|
a4a2a4088b | ||
|
|
c500cd1287 | ||
|
|
6f173c2ada | ||
|
|
34fb1bcfa4 | ||
|
|
58aa73b5be | ||
|
|
334aedd6c6 | ||
|
|
a6c6aa819c | ||
|
|
15c9a67028 | ||
|
|
42cb032bca | ||
|
|
a3a5709b70 | ||
|
|
3a4247c304 | ||
|
|
8eaadccda0 | ||
|
|
2b6b10788d | ||
|
|
26f8bbf7c6 | ||
|
|
252699d249 | ||
|
|
b2e55c4e2e | ||
|
|
d5ee372131 | ||
|
|
39588a4731 | ||
|
|
211559ed6e | ||
|
|
a637517413 | ||
|
|
ddbe0fa698 | ||
|
|
ee61ef3f12 | ||
|
|
54bfc65664 | ||
|
|
c159a5e42b | ||
|
|
1311e2d379 | ||
|
|
e6aa57275a | ||
|
|
d690ca2751 | ||
|
|
3e0a003872 | ||
|
|
e81a2ff577 |
No files matched your search
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# get build name from pubspec.yaml
|
||||
- name: Get build version
|
||||
@@ -19,7 +19,6 @@ jobs:
|
||||
echo "fetching version from pubspec.yaml..."
|
||||
echo "build_name=$(grep 'version: ' pubspec.yaml | awk '{print $2}')" >> $GITHUB_OUTPUT
|
||||
|
||||
|
||||
- uses: peter-evans/slash-command-dispatch@v4
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
|
||||
@@ -5,8 +5,7 @@ jobs:
|
||||
commitlint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: wagoid/commitlint-github-action@v4
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build the app
|
||||
shell: bash
|
||||
|
||||
@@ -9,6 +9,7 @@ on:
|
||||
- ".github/workflows/flutter_ci.yaml"
|
||||
- "frontend/rust-lib/**"
|
||||
- "frontend/appflowy_flutter/**"
|
||||
- "frontend/resources/**"
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
@@ -18,10 +19,11 @@ on:
|
||||
- ".github/workflows/flutter_ci.yaml"
|
||||
- "frontend/rust-lib/**"
|
||||
- "frontend/appflowy_flutter/**"
|
||||
- "frontend/resources/**"
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
FLUTTER_VERSION: "3.18.0-0.2.pre"
|
||||
FLUTTER_VERSION: "3.19.0"
|
||||
RUST_TOOLCHAIN: "1.75"
|
||||
CARGO_MAKE_VERSION: "0.36.6"
|
||||
|
||||
@@ -59,7 +61,7 @@ jobs:
|
||||
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
||||
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust toolchain
|
||||
id: rust_toolchain
|
||||
@@ -70,12 +72,20 @@ jobs:
|
||||
override: true
|
||||
profile: minimal
|
||||
|
||||
- name: Export pub environment variables and add to PATH
|
||||
run: |
|
||||
if [ "$RUNNER_OS" == "Windows" ]; then
|
||||
echo "PUB_CACHE=$LOCALAPPDATA\\Pub\\Cache" >> $GITHUB_ENV
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
- name: Install flutter
|
||||
id: flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "beta"
|
||||
channel: "stable"
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
cache: true
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
@@ -120,7 +130,7 @@ jobs:
|
||||
run: |
|
||||
tar -czf appflowy_flutter.tar.gz frontend/appflowy_flutter
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ github.run_id }}-${{ matrix.os }}
|
||||
path: appflowy_flutter.tar.gz
|
||||
@@ -140,7 +150,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust toolchain
|
||||
id: rust_toolchain
|
||||
@@ -155,8 +165,9 @@ jobs:
|
||||
id: flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "beta"
|
||||
channel: "stable"
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
cache: true
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
@@ -192,7 +203,7 @@ jobs:
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ github.run_id }}-${{ matrix.os }}
|
||||
|
||||
@@ -216,11 +227,11 @@ jobs:
|
||||
shell: bash
|
||||
|
||||
cloud_integration_test:
|
||||
needs: [ prepare ]
|
||||
needs: [prepare]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
os: [ubuntu-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
flutter_profile: development-linux-x86_64
|
||||
@@ -229,11 +240,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout appflowy cloud code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: AppFlowy-IO/AppFlowy-Cloud
|
||||
path: AppFlowy-Cloud
|
||||
depth: 1
|
||||
|
||||
- name: Prepare appflowy cloud env
|
||||
working-directory: AppFlowy-Cloud
|
||||
@@ -253,14 +263,15 @@ jobs:
|
||||
sleep 10
|
||||
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install flutter
|
||||
id: flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "beta"
|
||||
channel: "stable"
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
cache: true
|
||||
|
||||
- uses: taiki-e/install-action@v2
|
||||
with:
|
||||
@@ -280,7 +291,7 @@ jobs:
|
||||
flutter config --enable-linux-desktop
|
||||
shell: bash
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ github.run_id }}-${{ matrix.os }}
|
||||
|
||||
@@ -318,7 +329,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust toolchain
|
||||
id: rust_toolchain
|
||||
@@ -333,8 +344,9 @@ jobs:
|
||||
id: flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "beta"
|
||||
channel: "stable"
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
cache: true
|
||||
|
||||
- uses: taiki-e/install-action@v2
|
||||
with:
|
||||
@@ -363,7 +375,7 @@ jobs:
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ github.run_id }}-${{ matrix.os }}
|
||||
|
||||
@@ -409,7 +421,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust toolchain
|
||||
id: rust_toolchain
|
||||
@@ -424,8 +436,9 @@ jobs:
|
||||
id: flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "beta"
|
||||
channel: "stable"
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
cache: true
|
||||
|
||||
- uses: taiki-e/install-action@v2
|
||||
with:
|
||||
@@ -454,7 +467,7 @@ jobs:
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ github.run_id }}-${{ matrix.os }}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ on:
|
||||
- "!frontend/appflowy_tauri/**"
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.18.0-0.2.pre"
|
||||
FLUTTER_VERSION: "3.19.0"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
@@ -37,7 +37,17 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
# the following step is required to avoid running out of space
|
||||
- name: Check storage space
|
||||
run: df -h
|
||||
|
||||
- name: Maximize build space
|
||||
uses: easimon/maximize-build-space@master
|
||||
with:
|
||||
root-reserve-mb: 2048
|
||||
swap-size-mb: 1024
|
||||
remove-dotnet: 'true'
|
||||
|
||||
# the following step is required to avoid running out of space
|
||||
- name: Maximize build space
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
@@ -50,16 +60,18 @@ jobs:
|
||||
sudo rm -rf ${GITHUB_WORKSPACE}/.git
|
||||
sudo rm -rf $ANDROID_HOME/ndk
|
||||
|
||||
- name: Check storage space
|
||||
run: df -h
|
||||
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install flutter
|
||||
id: flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "beta"
|
||||
channel: "stable"
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
cache: true
|
||||
|
||||
- uses: nttld/setup-ndk@v1
|
||||
id: setup-ndk
|
||||
@@ -67,16 +79,10 @@ jobs:
|
||||
ndk-version: "r24"
|
||||
add-to-path: true
|
||||
|
||||
- uses: gradle/gradle-build-action@v2
|
||||
- uses: gradle/gradle-build-action@v3
|
||||
with:
|
||||
gradle-version: 7.6.3
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
prefix-key: ${{ matrix.os }}
|
||||
workspaces: |
|
||||
frontend/rust-lib
|
||||
|
||||
- uses: davidB/rust-cargo-make@v1
|
||||
with:
|
||||
version: "0.36.6"
|
||||
|
||||
@@ -6,7 +6,7 @@ on:
|
||||
- "*"
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.18.0-0.2.pre"
|
||||
FLUTTER_VERSION: "3.19.0"
|
||||
RUST_TOOLCHAIN: "1.75"
|
||||
|
||||
jobs:
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build release notes
|
||||
run: |
|
||||
@@ -52,12 +52,12 @@ jobs:
|
||||
- { target: x86_64-pc-windows-msvc, os: windows-2019 }
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "beta"
|
||||
channel: "stable"
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
|
||||
- name: Install Rust toolchain
|
||||
@@ -138,12 +138,12 @@ jobs:
|
||||
- { target: x86_64-apple-darwin, os: macos-11, extra-build-args: "" }
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "beta"
|
||||
channel: "stable"
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
|
||||
- name: Install Rust toolchain
|
||||
@@ -238,12 +238,12 @@ jobs:
|
||||
}
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "beta"
|
||||
channel: "stable"
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
|
||||
- name: Install Rust toolchain
|
||||
@@ -343,12 +343,12 @@ jobs:
|
||||
}
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "beta"
|
||||
channel: "stable"
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
|
||||
- name: Install Rust toolchain
|
||||
@@ -455,19 +455,19 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./frontend/scripts/docker-buildfiles/Dockerfile
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust toolchain
|
||||
id: rust_toolchain
|
||||
@@ -45,16 +45,15 @@ jobs:
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
prefix-key: 'ubuntu-latest'
|
||||
prefix-key: "ubuntu-latest"
|
||||
workspaces: |
|
||||
frontend/rust-lib
|
||||
|
||||
- name: Checkout appflowy cloud code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: AppFlowy-IO/AppFlowy-Cloud
|
||||
path: AppFlowy-Cloud
|
||||
depth: 1
|
||||
|
||||
- name: Prepare appflowy cloud env
|
||||
working-directory: AppFlowy-Cloud
|
||||
@@ -80,7 +79,6 @@ jobs:
|
||||
af_cloud_test_gotrue_url: http://localhost/gotrue
|
||||
run: cargo test --no-default-features --features="rev-sqlite,dart" -- --nocapture
|
||||
|
||||
|
||||
- name: rustfmt rust-lib
|
||||
run: cargo fmt --all -- --check
|
||||
working-directory: frontend/rust-lib/
|
||||
|
||||
@@ -10,7 +10,7 @@ on:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
FLUTTER_VERSION: "3.18.0-0.2.pre"
|
||||
FLUTTER_VERSION: "3.19.0"
|
||||
RUST_TOOLCHAIN: "1.75"
|
||||
|
||||
jobs:
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust toolchain
|
||||
id: rust_toolchain
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
id: flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "beta"
|
||||
channel: "stable"
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
cache: true
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ on:
|
||||
- ".github/workflows/tauri_ci.yaml"
|
||||
- "frontend/rust-lib/**"
|
||||
- "frontend/appflowy_tauri/**"
|
||||
- "frontend/resources/**"
|
||||
|
||||
env:
|
||||
NODE_VERSION: "18.16.0"
|
||||
@@ -21,8 +22,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# platform: [macos-latest, ubuntu-latest, windows-latest]
|
||||
platform: [ubuntu-latest]
|
||||
platform: [ubuntu-latest]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
|
||||
@@ -26,9 +26,9 @@ jobs:
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
@@ -40,14 +40,16 @@ jobs:
|
||||
frontend/rust-lib
|
||||
frontend/appflowy_web/appflowy_wasm
|
||||
|
||||
# TODO: Can combine caching deps and node_modules in one
|
||||
# See Glob patterns: https://github.com/actions/toolkit/tree/main/packages/glob
|
||||
- name: Cache Node.js dependencies
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: npm-${{ runner.os }}
|
||||
|
||||
- name: Cache node_modules
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: frontend/appflowy_web/node_modules
|
||||
key: node-modules-${{ runner.os }}
|
||||
|
||||
+24
-1
@@ -1,5 +1,28 @@
|
||||
# Release Notes
|
||||
## Version 0.4.7 - 02/08/2024
|
||||
## Version 0.5.0 - 02/26/2024
|
||||
### New Features
|
||||
- Added support for scaling text on mobile platforms for better readability.
|
||||
- Introduced a toggle for favorites directly from the documents' top bar.
|
||||
- Optimized the image upload process and added error messaging for failed uploads.
|
||||
- Implemented depth control for outline block components.
|
||||
- New checklist task creation is now more intuitive, with prompts appearing on hover over list items in the row detail page.
|
||||
- Enhanced sorting capabilities, allowing reordering and addition of multiple sorts.
|
||||
- Expanded sorting and filtering options to include more field types like checklist, creation time, and modification time.
|
||||
- Added support for field calculations within databases.
|
||||
### Bug Fixes
|
||||
- Fixed an issue where inserting an image from Unsplash in local mode was not possible.
|
||||
- Fixed undo/redo functionality in lists.
|
||||
- Fixed data loss issues when converting between block types.
|
||||
- Fixed a bug where newly created rows were not being automatically sorted.
|
||||
- Fixed issues related to deleting a sorting field or sort not removing existing sorts properly.
|
||||
### Notes
|
||||
- Windows 7, Windows 8, and iOS 11 are not yet supported due to the upgrade to Flutter 3.19.0.
|
||||
|
||||
## Version 0.4.9 - 02/17/2024
|
||||
### Bug Fixes
|
||||
- Resolved the issue that caused users to be redirected to the Sign In page
|
||||
|
||||
## Version 0.4.8 - 02/13/2024
|
||||
### Bug Fixes
|
||||
- Fixed a possible error when loading workspaces
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -12,7 +12,7 @@
|
||||
"program": "./lib/main.dart",
|
||||
"type": "dart",
|
||||
"env": {
|
||||
"RUST_LOG": "trace",
|
||||
"RUST_LOG": "debug",
|
||||
"RUST_BACKTRACE": "1"
|
||||
},
|
||||
// uncomment the following line to testing performance.
|
||||
|
||||
@@ -26,7 +26,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
|
||||
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
|
||||
CARGO_MAKE_CRATE_NAME = "dart-ffi"
|
||||
LIB_NAME = "dart_ffi"
|
||||
APPFLOWY_VERSION = "0.4.7"
|
||||
APPFLOWY_VERSION = "0.5.1"
|
||||
FLUTTER_DESKTOP_FEATURES = "dart,rev-sqlite"
|
||||
PRODUCT_NAME = "AppFlowy"
|
||||
MACOSX_DEPLOYMENT_TARGET = "11.0"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int64_t init_sdk(char *data);
|
||||
int64_t init_sdk(int64_t port, char *data);
|
||||
|
||||
void async_event(int64_t port, const uint8_t *input, uintptr_t len);
|
||||
|
||||
@@ -13,6 +13,6 @@ int32_t set_stream_port(int64_t port);
|
||||
|
||||
void link_me_please(void);
|
||||
|
||||
void backend_log(int64_t level, const char *data);
|
||||
void rust_log(int64_t level, const char *data);
|
||||
|
||||
void set_env(const char *data);
|
||||
@@ -40,19 +40,19 @@ void main() {
|
||||
|
||||
await tester.openSettings();
|
||||
await tester.openSettingsPage(SettingsPage.user);
|
||||
final userAvatarFinder = find.descendant(
|
||||
of: find.byType(SettingsUserView),
|
||||
matching: find.byType(UserAvatar),
|
||||
);
|
||||
// final userAvatarFinder = find.descendant(
|
||||
// of: find.byType(SettingsUserView),
|
||||
// matching: find.byType(UserAvatar),
|
||||
// );
|
||||
|
||||
// Open icon picker dialog and select emoji
|
||||
await tester.tap(userAvatarFinder);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tapEmoji('😁');
|
||||
await tester.pumpAndSettle();
|
||||
final UserAvatar userAvatar =
|
||||
tester.widget(userAvatarFinder) as UserAvatar;
|
||||
expect(userAvatar.iconUrl, '😁');
|
||||
// await tester.tap(userAvatarFinder);
|
||||
// await tester.pumpAndSettle();
|
||||
// await tester.tapEmoji('😁');
|
||||
// await tester.pumpAndSettle();
|
||||
// final UserAvatar userAvatar =
|
||||
// tester.widget(userAvatarFinder) as UserAvatar;
|
||||
// expect(userAvatar.iconUrl, '😁');
|
||||
|
||||
// enter user name
|
||||
final userNameFinder = find.descendant(
|
||||
@@ -81,12 +81,12 @@ void main() {
|
||||
await tester.openSettingsPage(SettingsPage.user);
|
||||
|
||||
// verify icon
|
||||
final userAvatarFinder = find.descendant(
|
||||
of: find.byType(SettingsUserView),
|
||||
matching: find.byType(UserAvatar),
|
||||
);
|
||||
final UserAvatar userAvatar = tester.widget(userAvatarFinder) as UserAvatar;
|
||||
expect(userAvatar.iconUrl, '😁');
|
||||
// final userAvatarFinder = find.descendant(
|
||||
// of: find.byType(SettingsUserView),
|
||||
// matching: find.byType(UserAvatar),
|
||||
// );
|
||||
// final UserAvatar userAvatar = tester.widget(userAvatarFinder) as UserAvatar;
|
||||
// expect(userAvatar.iconUrl, '😁');
|
||||
|
||||
// verify name
|
||||
final userNameFinder = find.descendant(
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'package:appflowy/plugins/database/calendar/presentation/calendar_event_editor.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pbenum.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
@@ -147,6 +149,49 @@ void main() {
|
||||
tester.assertNumberOfEventsOnSpecificDay(1, DateTime.now());
|
||||
});
|
||||
|
||||
testWidgets('create and duplicate calendar event', (tester) async {
|
||||
const customTitle = "EventTitleCustom";
|
||||
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
// Create the calendar view
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
layout: ViewLayoutPB.Calendar,
|
||||
);
|
||||
|
||||
// Scroll until today's date cell is visible
|
||||
await tester.scrollToToday();
|
||||
|
||||
// Hover over today's calendar cell
|
||||
await tester.hoverOnTodayCalendarCell(
|
||||
// Tap on create new event button
|
||||
onHover: () async => tester.tapAddCalendarEventButton(),
|
||||
);
|
||||
|
||||
// Make sure that the event editor popup is shown
|
||||
tester.assertEventEditorOpen();
|
||||
|
||||
tester.assertNumberOfEventsInCalendar(1);
|
||||
|
||||
// Change the title of the event
|
||||
await tester.editEventTitle(customTitle);
|
||||
|
||||
// Duplicate event
|
||||
final duplicateBtnFinder = find
|
||||
.descendant(
|
||||
of: find.byType(CalendarEventEditor),
|
||||
matching: find.byType(
|
||||
FlowyIconButton,
|
||||
),
|
||||
)
|
||||
.first;
|
||||
await tester.tap(duplicateBtnFinder);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
tester.assertNumberOfEventsInCalendar(2, title: customTitle);
|
||||
});
|
||||
|
||||
testWidgets('rescheduling events', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
@@ -9,7 +9,7 @@ import '../util/database_test_op.dart';
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('database filter', () {
|
||||
group('grid filter:', () {
|
||||
testWidgets('add text filter', (tester) async {
|
||||
await tester.openV020database();
|
||||
|
||||
|
||||
+3
-4
@@ -1,4 +1,3 @@
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
@@ -10,7 +9,7 @@ void main() {
|
||||
|
||||
group('text direction', () {
|
||||
testWidgets(
|
||||
'''no text direction items will be displayed in the default/LTR mode,and three text direction items will be displayed in the RTL mode.''',
|
||||
'''no text direction items will be displayed in the default/LTR mode, and three text direction items will be displayed when toggle is enabled.''',
|
||||
(tester) async {
|
||||
// combine the two tests into one to avoid the time-consuming process of initializing the app
|
||||
await tester.initializeAppFlowy();
|
||||
@@ -32,7 +31,7 @@ void main() {
|
||||
'toolbar/text_direction_ltr',
|
||||
'toolbar/text_direction_rtl',
|
||||
];
|
||||
// no text direction items in default/LTR mode
|
||||
// no text direction items by default
|
||||
var button = find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is SVGIconItemWidget &&
|
||||
@@ -41,7 +40,7 @@ void main() {
|
||||
expect(button, findsNothing);
|
||||
|
||||
// switch to the RTL mode
|
||||
await tester.switchLayoutDirectionMode(LayoutDirection.rtlLayout);
|
||||
await tester.toggleEnableRTLToolbarItems();
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.editor.updateSelection(selection);
|
||||
|
||||
+109
-9
@@ -1,11 +1,18 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/outline/outline_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../util/util.dart';
|
||||
|
||||
const String heading1 = "Heading 1";
|
||||
const String heading2 = "Heading 2";
|
||||
const String heading3 = "Heading 3";
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
@@ -33,12 +40,8 @@ void main() {
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'outline_test',
|
||||
);
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
|
||||
await tester.ime.insertText('# Heading 1\n');
|
||||
await tester.ime.insertText('## Heading 2\n');
|
||||
await tester.ime.insertText('### Heading 3\n');
|
||||
|
||||
await insertHeadingComponent(tester);
|
||||
/* Results in:
|
||||
* # Heading 1
|
||||
* ## Heading 2
|
||||
@@ -51,7 +54,7 @@ void main() {
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(OutlineBlockWidget),
|
||||
matching: find.text('Heading 1'),
|
||||
matching: find.text(heading1),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
@@ -60,7 +63,7 @@ void main() {
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(OutlineBlockWidget),
|
||||
matching: find.text('Heading 2'),
|
||||
matching: find.text(heading2),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
@@ -69,7 +72,7 @@ void main() {
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(OutlineBlockWidget),
|
||||
matching: find.text('Heading 3'),
|
||||
matching: find.text(heading3),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
@@ -80,11 +83,86 @@ void main() {
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(OutlineBlockWidget),
|
||||
matching: find.text('Heading 1Hello world'),
|
||||
matching: find.text('${heading1}Hello world'),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets("control the depth of outline block", (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'outline_test',
|
||||
);
|
||||
|
||||
await insertHeadingComponent(tester);
|
||||
/* Results in:
|
||||
* # Heading 1
|
||||
* ## Heading 2
|
||||
* ### Heading 3
|
||||
*/
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(3);
|
||||
await insertOutlineInDocument(tester);
|
||||
|
||||
// expect to find only the `heading1` widget under the [OutlineBlockWidget]
|
||||
await hoverAndClickDepthOptionAction(tester, [3], 1);
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(OutlineBlockWidget),
|
||||
matching: find.text(heading2),
|
||||
),
|
||||
findsNothing,
|
||||
);
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(OutlineBlockWidget),
|
||||
matching: find.text(heading3),
|
||||
),
|
||||
findsNothing,
|
||||
);
|
||||
//////
|
||||
|
||||
/// expect to find only the 'heading1' and 'heading2' under the [OutlineBlockWidget]
|
||||
await hoverAndClickDepthOptionAction(tester, [3], 2);
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(OutlineBlockWidget),
|
||||
matching: find.text(heading3),
|
||||
),
|
||||
findsNothing,
|
||||
);
|
||||
//////
|
||||
|
||||
// expect to find all the headings under the [OutlineBlockWidget]
|
||||
await hoverAndClickDepthOptionAction(tester, [3], 3);
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(OutlineBlockWidget),
|
||||
matching: find.text(heading1),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(OutlineBlockWidget),
|
||||
matching: find.text(heading2),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(OutlineBlockWidget),
|
||||
matching: find.text(heading3),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
//////
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -97,3 +175,25 @@ Future<void> insertOutlineInDocument(WidgetTester tester) async {
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
Future<void> hoverAndClickDepthOptionAction(
|
||||
WidgetTester tester,
|
||||
List<int> path,
|
||||
int level,
|
||||
) async {
|
||||
await tester.editor.hoverAndClickOptionMenuButton([3]);
|
||||
await tester.tap(find.byType(AppFlowyPopover).hitTestable().last);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Find a total of 4 HoverButtons under the [BlockOptionButton],
|
||||
// in addition to 3 HoverButtons under the [DepthOptionAction] - (child of BlockOptionButton)
|
||||
await tester.tap(find.byType(HoverButton).hitTestable().at(3 + level));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
Future<void> insertHeadingComponent(WidgetTester tester) async {
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.ime.insertText('# $heading1\n');
|
||||
await tester.ime.insertText('## $heading2\n');
|
||||
await tester.ime.insertText('### $heading3\n');
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import 'dart:io';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/emoji_picker/emoji_picker.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/editor/editor_component/service/editor.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'util/keyboard.dart';
|
||||
import 'util/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
// May be better to move this to an existing test but unsure what it fits with
|
||||
group('Keyboard shortcuts related to emojis', () {
|
||||
testWidgets('cmd/ctrl+alt+e shortcut opens the emoji picker',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
final Finder editor = find.byType(AppFlowyEditor);
|
||||
await tester.tap(editor);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(EmojiSelectionMenu), findsNothing);
|
||||
|
||||
await FlowyTestKeyboard.simulateKeyDownEvent(
|
||||
[
|
||||
Platform.isMacOS
|
||||
? LogicalKeyboardKey.meta
|
||||
: LogicalKeyboardKey.control,
|
||||
LogicalKeyboardKey.alt,
|
||||
LogicalKeyboardKey.keyE,
|
||||
],
|
||||
tester: tester,
|
||||
);
|
||||
|
||||
expect(find.byType(EmojiSelectionMenu), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import 'share_markdown_test.dart' as share_markdown_test;
|
||||
import 'sidebar/sidebar_test_runner.dart' as sidebar_test_runner;
|
||||
import 'switch_folder_test.dart' as switch_folder_test;
|
||||
import 'tabs_test.dart' as tabs_test;
|
||||
import 'emoji_shortcut_test.dart' as emoji_shortcut_test;
|
||||
// import 'auth/supabase_auth_test.dart' as supabase_auth_test_runner;
|
||||
|
||||
/// The main task runner for all integration tests in AppFlowy.
|
||||
@@ -69,6 +70,7 @@ Future<void> main() async {
|
||||
|
||||
// Others
|
||||
hotkeys_test.main();
|
||||
emoji_shortcut_test.main();
|
||||
|
||||
// Appearance integration test
|
||||
appearance_test_runner.main();
|
||||
|
||||
@@ -306,7 +306,7 @@ extension CommonOperations on WidgetTester {
|
||||
KVKeys.showRenameDialogWhenCreatingNewFile,
|
||||
(value) => bool.parse(value),
|
||||
);
|
||||
final showRenameDialog = settingsOrFailure.fold(() => false, (r) => r);
|
||||
final showRenameDialog = settingsOrFailure ?? false;
|
||||
if (showRenameDialog) {
|
||||
await tapOKButton();
|
||||
}
|
||||
|
||||
@@ -1116,7 +1116,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
|
||||
/// Must call [tapSortMenuInSettingBar] first.
|
||||
Future<void> tapAllSortButton() async {
|
||||
await tapButton(find.byType(DatabaseDeleteSortButton));
|
||||
await tapButton(find.byType(DeleteAllSortsButton));
|
||||
}
|
||||
|
||||
Future<void> scrollOptionFilterListByOffset(Offset offset) async {
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:appflowy/plugins/base/emoji/emoji_picker.dart';
|
||||
import 'package:appflowy/plugins/base/emoji/emoji_skin_tone.dart';
|
||||
import 'package:appflowy/plugins/base/icon/icon_picker.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_add_button.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_option_button.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/cover_editor.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart';
|
||||
@@ -241,4 +242,25 @@ class EditorOperations {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// hover and click on the option menu button beside the block component.
|
||||
Future<void> hoverAndClickOptionMenuButton(Path path) async {
|
||||
final optionMenuButton = find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is BlockComponentActionWrapper &&
|
||||
widget.node.path.equals(path),
|
||||
);
|
||||
await tester.hoverOnWidget(
|
||||
optionMenuButton,
|
||||
onHover: () async {
|
||||
await tester.tapButton(
|
||||
find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is BlockOptionButton &&
|
||||
widget.blockComponentContext.node.path.equals(path),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_user.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_setting.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/settings_dialog.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/direction_setting.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu_element.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_user_view.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'base.dart';
|
||||
@@ -76,31 +75,15 @@ extension AppFlowySettings on WidgetTester {
|
||||
await pumpAndSettle();
|
||||
}
|
||||
|
||||
// go to settings page and switch the layout direction
|
||||
Future<void> switchLayoutDirectionMode(
|
||||
LayoutDirection layoutDirection,
|
||||
) async {
|
||||
// go to settings page and toggle enable RTL toolbar items
|
||||
Future<void> toggleEnableRTLToolbarItems() async {
|
||||
await openSettings();
|
||||
await openSettingsPage(SettingsPage.appearance);
|
||||
|
||||
final button = find.byKey(const ValueKey('layout_direction_option_button'));
|
||||
expect(button, findsOneWidget);
|
||||
await tapButton(button);
|
||||
|
||||
switch (layoutDirection) {
|
||||
case LayoutDirection.ltrLayout:
|
||||
final ltrButton = find.text(
|
||||
LocaleKeys.settings_appearance_layoutDirection_ltr.tr(),
|
||||
);
|
||||
await tapButton(ltrButton);
|
||||
break;
|
||||
case LayoutDirection.rtlLayout:
|
||||
final rtlButton = find.text(
|
||||
LocaleKeys.settings_appearance_layoutDirection_rtl.tr(),
|
||||
);
|
||||
await tapButton(rtlButton);
|
||||
break;
|
||||
}
|
||||
final switchButton =
|
||||
find.byKey(EnableRTLToolbarItemsSetting.enableRTLSwitchKey);
|
||||
expect(switchButton, findsOneWidget);
|
||||
await tapButton(switchButton);
|
||||
|
||||
// tap anywhere to close the settings page
|
||||
await tapAt(Offset.zero);
|
||||
|
||||
@@ -21,6 +21,6 @@
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>11.0</string>
|
||||
<string>12.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,5 +1,5 @@
|
||||
# Uncomment this line to define a global platform for your project
|
||||
# platform :ios, '11.0'
|
||||
# platform :ios, '12.0'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
@@ -169,8 +169,8 @@ SPEC CHECKSUMS:
|
||||
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
|
||||
file_picker: 15fd9539e4eb735dc54bae8c0534a7a9511a03de
|
||||
flowy_infra_ui: 0455e1fa8c51885aa1437848e361e99419f34ebc
|
||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||
fluttertoast: fafc4fa4d01a6a9e4f772ecd190ffa525e9e2d9c
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
|
||||
image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb
|
||||
image_picker_ios: 99dfe1854b4fa34d0364e74a78448a0151025425
|
||||
integration_test: 13825b8a9334a850581300559b8839134b124670
|
||||
@@ -189,6 +189,6 @@ SPEC CHECKSUMS:
|
||||
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
|
||||
url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812
|
||||
|
||||
PODFILE CHECKSUM: 8c681999c7764593c94846b2a64b44d86f7a27ac
|
||||
PODFILE CHECKSUM: d94f9be27d1db182e9bc77d10f065555d518f127
|
||||
|
||||
COCOAPODS: 1.12.1
|
||||
COCOAPODS: 1.15.2
|
||||
@@ -143,7 +143,7 @@
|
||||
97C146E61CF9000F007C117D /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1430;
|
||||
LastUpgradeCheck = 1510;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
97C146ED1CF9000F007C117D = {
|
||||
@@ -330,7 +330,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
@@ -419,7 +419,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -468,7 +468,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1430"
|
||||
LastUpgradeVersion = "1510"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
abstract class KeyValueStorage {
|
||||
Future<void> set(String key, String value);
|
||||
Future<Option<String>> get(String key);
|
||||
Future<Option<T>> getWithFormat<T>(
|
||||
Future<String?> get(String key);
|
||||
Future<T?> getWithFormat<T>(
|
||||
String key,
|
||||
T Function(String value) formatter,
|
||||
);
|
||||
@@ -17,26 +16,26 @@ class DartKeyValue implements KeyValueStorage {
|
||||
SharedPreferences get sharedPreferences => _sharedPreferences!;
|
||||
|
||||
@override
|
||||
Future<Option<String>> get(String key) async {
|
||||
Future<String?> get(String key) async {
|
||||
await _initSharedPreferencesIfNeeded();
|
||||
|
||||
final value = sharedPreferences.getString(key);
|
||||
if (value != null) {
|
||||
return Some(value);
|
||||
return value;
|
||||
}
|
||||
return none();
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Option<T>> getWithFormat<T>(
|
||||
Future<T?> getWithFormat<T>(
|
||||
String key,
|
||||
T Function(String value) formatter,
|
||||
) async {
|
||||
final value = await get(key);
|
||||
return value.fold(
|
||||
() => none(),
|
||||
(s) => Some(formatter(s)),
|
||||
);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return formatter(value);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -49,6 +49,13 @@ class KVKeys {
|
||||
|
||||
static const String kCloudType = 'kCloudType';
|
||||
static const String kAppflowyCloudBaseURL = 'kAppFlowyCloudBaseURL';
|
||||
static const String kSupabaseURL = 'kSupbaseURL';
|
||||
static const String kSupabaseURL = 'kSupabaseURL';
|
||||
static const String kSupabaseAnonKey = 'kSupabaseAnonKey';
|
||||
|
||||
/// The key for saving the text scale factor.
|
||||
///
|
||||
/// The value is a double string.
|
||||
/// The value range is from 0.8 to 1.0. If it's greater than 1.0, it will cause
|
||||
/// the text to be too large and not aligned with the icon
|
||||
static const String textScaleFactor = 'textScaleFactor';
|
||||
}
|
||||
@@ -1,2 +1 @@
|
||||
export 'target_platform.dart';
|
||||
export 'url_validator.dart';
|
||||
@@ -0,0 +1,50 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:url_launcher/url_launcher.dart' as launcher;
|
||||
|
||||
typedef OnFailureCallback = void Function(Uri uri);
|
||||
|
||||
Future<bool> afLaunchUrl(
|
||||
Uri uri, {
|
||||
BuildContext? context,
|
||||
OnFailureCallback? onFailure,
|
||||
launcher.LaunchMode mode = launcher.LaunchMode.platformDefault,
|
||||
String? webOnlyWindowName,
|
||||
}) async {
|
||||
try {
|
||||
return await launcher.launchUrl(
|
||||
uri,
|
||||
mode: mode,
|
||||
webOnlyWindowName: webOnlyWindowName,
|
||||
);
|
||||
} on PlatformException catch (e) {
|
||||
Log.error("Failed to open uri: $e");
|
||||
if (onFailure != null) {
|
||||
onFailure(uri);
|
||||
} else {
|
||||
showMessageToast(
|
||||
LocaleKeys.failedToOpenUrl.tr(args: [e.message ?? "PlatformException"]),
|
||||
context: context,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<void> afLaunchUrlString(String url) async {
|
||||
try {
|
||||
final uri = Uri.parse(url);
|
||||
|
||||
await launcher.launchUrl(uri);
|
||||
} on PlatformException catch (e) {
|
||||
Log.error("Failed to open uri: $e");
|
||||
} on FormatException catch (e) {
|
||||
Log.error("Failed to parse url: $e");
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
part 'url_validator.freezed.dart';
|
||||
|
||||
Either<UriFailure, Uri> parseValidUrl(String url) {
|
||||
try {
|
||||
final uri = Uri.parse(url);
|
||||
if (uri.scheme.isEmpty || uri.host.isEmpty) {
|
||||
return left(const UriFailure.invalidSchemeHost());
|
||||
}
|
||||
return right(uri);
|
||||
} on FormatException {
|
||||
return left(const UriFailure.invalidUriFormat());
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class UriFailure with _$UriFailure {
|
||||
const factory UriFailure.invalidSchemeHost() = _InvalidSchemeHost;
|
||||
const factory UriFailure.invalidUriFormat() = _InvalidUriFormat;
|
||||
}
|
||||
@@ -3,11 +3,11 @@ import 'dart:typed_data';
|
||||
import 'package:appflowy/core/notification/notification_helper.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-document/notification.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
|
||||
typedef DocumentNotificationCallback = void Function(
|
||||
DocumentNotification,
|
||||
Either<Uint8List, FlowyError>,
|
||||
FlowyResult<Uint8List, FlowyError>,
|
||||
);
|
||||
|
||||
class DocumentNotificationParser
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import 'package:appflowy_backend/protobuf/flowy-notification/protobuf.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/notification.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-notification/protobuf.dart';
|
||||
import 'package:appflowy_backend/rust_stream.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
|
||||
import 'notification_helper.dart';
|
||||
|
||||
// Folder
|
||||
typedef FolderNotificationCallback = void Function(
|
||||
FolderNotification,
|
||||
Either<Uint8List, FlowyError>,
|
||||
FlowyResult<Uint8List, FlowyError>,
|
||||
);
|
||||
|
||||
class FolderNotificationParser
|
||||
@@ -27,7 +28,7 @@ class FolderNotificationParser
|
||||
|
||||
typedef FolderNotificationHandler = Function(
|
||||
FolderNotification ty,
|
||||
Either<Uint8List, FlowyError> result,
|
||||
FlowyResult<Uint8List, FlowyError> result,
|
||||
);
|
||||
|
||||
class FolderNotificationListener {
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import 'package:appflowy_backend/protobuf/flowy-notification/protobuf.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/notification.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-notification/protobuf.dart';
|
||||
import 'package:appflowy_backend/rust_stream.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
|
||||
import 'notification_helper.dart';
|
||||
|
||||
// DatabasePB
|
||||
typedef DatabaseNotificationCallback = void Function(
|
||||
DatabaseNotification,
|
||||
Either<Uint8List, FlowyError>,
|
||||
FlowyResult<Uint8List, FlowyError>,
|
||||
);
|
||||
|
||||
class DatabaseNotificationParser
|
||||
@@ -27,7 +28,7 @@ class DatabaseNotificationParser
|
||||
|
||||
typedef DatabaseNotificationHandler = Function(
|
||||
DatabaseNotification ty,
|
||||
Either<Uint8List, FlowyError> result,
|
||||
FlowyResult<Uint8List, FlowyError> result,
|
||||
);
|
||||
|
||||
class DatabaseNotificationListener {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:appflowy_backend/protobuf/flowy-notification/protobuf.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
|
||||
class NotificationParser<T, E> {
|
||||
NotificationParser({
|
||||
@@ -11,7 +12,7 @@ class NotificationParser<T, E> {
|
||||
});
|
||||
|
||||
String? id;
|
||||
void Function(T, Either<Uint8List, E>) callback;
|
||||
void Function(T, FlowyResult<Uint8List, E>) callback;
|
||||
E Function(Uint8List) errorParser;
|
||||
T? Function(int) tyParser;
|
||||
|
||||
@@ -30,10 +31,10 @@ class NotificationParser<T, E> {
|
||||
if (subject.hasError()) {
|
||||
final bytes = Uint8List.fromList(subject.error);
|
||||
final error = errorParser(bytes);
|
||||
callback(ty, right(error));
|
||||
callback(ty, FlowyResult.failure(error));
|
||||
} else {
|
||||
final bytes = Uint8List.fromList(subject.payload);
|
||||
callback(ty, left(bytes));
|
||||
callback(ty, FlowyResult.success(bytes));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,18 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-notification/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/rust_stream.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
|
||||
import 'notification_helper.dart';
|
||||
|
||||
// User
|
||||
typedef UserNotificationCallback = void Function(
|
||||
UserNotification,
|
||||
Either<Uint8List, FlowyError>,
|
||||
FlowyResult<Uint8List, FlowyError>,
|
||||
);
|
||||
|
||||
class UserNotificationParser
|
||||
@@ -27,7 +28,7 @@ class UserNotificationParser
|
||||
|
||||
typedef UserNotificationHandler = Function(
|
||||
UserNotification ty,
|
||||
Either<Uint8List, FlowyError> result,
|
||||
FlowyResult<Uint8List, FlowyError> result,
|
||||
);
|
||||
|
||||
class UserNotificationListener {
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
extension RawKeyboardExtension on RawKeyboard {
|
||||
bool get isAltPressed => keysPressed.any(
|
||||
(key) => [
|
||||
LogicalKeyboardKey.alt,
|
||||
LogicalKeyboardKey.altLeft,
|
||||
LogicalKeyboardKey.altRight,
|
||||
].contains(key),
|
||||
);
|
||||
|
||||
bool get isControlPressed => keysPressed.any(
|
||||
(key) => [
|
||||
LogicalKeyboardKey.control,
|
||||
LogicalKeyboardKey.controlLeft,
|
||||
LogicalKeyboardKey.controlRight,
|
||||
].contains(key),
|
||||
);
|
||||
}
|
||||
@@ -1,19 +1,25 @@
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-date/entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
|
||||
class DateService {
|
||||
static Future<Either<FlowyError, DateTime>> queryDate(String search) async {
|
||||
static Future<FlowyResult<DateTime, FlowyError>> queryDate(
|
||||
String search,
|
||||
) async {
|
||||
final query = DateQueryPB.create()..query = search;
|
||||
final result = (await DateEventQueryDate(query).send()).swap();
|
||||
return result.fold((l) => left(l), (r) {
|
||||
final date = DateTime.tryParse(r.date);
|
||||
if (date != null) {
|
||||
return right(date);
|
||||
}
|
||||
|
||||
return left(FlowyError(msg: 'Could not parse Date (NLP) from String'));
|
||||
});
|
||||
final result = await DateEventQueryDate(query).send();
|
||||
return result.fold(
|
||||
(s) {
|
||||
final date = DateTime.tryParse(s.date);
|
||||
if (date != null) {
|
||||
return FlowyResult.success(date);
|
||||
}
|
||||
return FlowyResult.failure(
|
||||
FlowyError(msg: 'Could not parse Date (NLP) from String'),
|
||||
);
|
||||
},
|
||||
(e) => FlowyResult.failure(e),
|
||||
);
|
||||
}
|
||||
}
|
||||
+27
-37
@@ -4,7 +4,6 @@ import 'package:appflowy/env/backend_env.dart';
|
||||
import 'package:appflowy/env/env.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
|
||||
/// Sets the cloud type for the application.
|
||||
///
|
||||
@@ -52,7 +51,7 @@ const String kAppflowyCloudUrl = "https://beta.appflowy.cloud";
|
||||
///
|
||||
Future<AuthenticatorType> getAuthenticatorType() async {
|
||||
final value = await getIt<KeyValueStorage>().get(KVKeys.kCloudType);
|
||||
if (value.isNone() && !integrationMode().isUnitTest) {
|
||||
if (value == null && !integrationMode().isUnitTest) {
|
||||
// if the cloud type is not set, then set it to AppFlowy Cloud as default.
|
||||
await useAppFlowyBetaCloudWithURL(
|
||||
kAppflowyCloudUrl,
|
||||
@@ -61,7 +60,7 @@ Future<AuthenticatorType> getAuthenticatorType() async {
|
||||
return AuthenticatorType.appflowyCloud;
|
||||
}
|
||||
|
||||
switch (value.getOrElse(() => "0")) {
|
||||
switch (value ?? "0") {
|
||||
case "0":
|
||||
return AuthenticatorType.local;
|
||||
case "1":
|
||||
@@ -177,16 +176,13 @@ AuthenticatorType currentCloudType() {
|
||||
return getIt<AppFlowyCloudSharedEnv>().authenticatorType;
|
||||
}
|
||||
|
||||
Future<void> _setAppFlowyCloudUrl(Option<String> url) async {
|
||||
await url.fold(
|
||||
() => getIt<KeyValueStorage>().set(KVKeys.kAppflowyCloudBaseURL, ""),
|
||||
(s) => getIt<KeyValueStorage>().set(KVKeys.kAppflowyCloudBaseURL, s),
|
||||
);
|
||||
Future<void> _setAppFlowyCloudUrl(String? url) async {
|
||||
await getIt<KeyValueStorage>().set(KVKeys.kAppflowyCloudBaseURL, url ?? '');
|
||||
}
|
||||
|
||||
Future<void> useSelfHostedAppFlowyCloudWithURL(String url) async {
|
||||
await _setAuthenticatorType(AuthenticatorType.appflowyCloudSelfHost);
|
||||
await _setAppFlowyCloudUrl(Some(url));
|
||||
await _setAppFlowyCloudUrl(url);
|
||||
}
|
||||
|
||||
Future<void> useAppFlowyBetaCloudWithURL(
|
||||
@@ -194,7 +190,7 @@ Future<void> useAppFlowyBetaCloudWithURL(
|
||||
AuthenticatorType authenticatorType,
|
||||
) async {
|
||||
await _setAuthenticatorType(authenticatorType);
|
||||
await _setAppFlowyCloudUrl(Some(url));
|
||||
await _setAppFlowyCloudUrl(url);
|
||||
}
|
||||
|
||||
Future<void> useLocalServer() async {
|
||||
@@ -206,7 +202,7 @@ Future<void> useSupabaseCloud({
|
||||
required String anonKey,
|
||||
}) async {
|
||||
await _setAuthenticatorType(AuthenticatorType.supabase);
|
||||
await setSupbaseServer(Some(url), Some(anonKey));
|
||||
await setSupabaseServer(url, anonKey);
|
||||
}
|
||||
|
||||
/// Use getIt<AppFlowyCloudSharedEnv>() to get the shared environment.
|
||||
@@ -314,10 +310,7 @@ Future<AppFlowyCloudConfiguration> getAppFlowyCloudConfig(
|
||||
Future<String> getAppFlowyCloudUrl() async {
|
||||
final result =
|
||||
await getIt<KeyValueStorage>().get(KVKeys.kAppflowyCloudBaseURL);
|
||||
return result.fold(
|
||||
() => kAppflowyCloudUrl,
|
||||
(url) => url,
|
||||
);
|
||||
return result ?? kAppflowyCloudUrl;
|
||||
}
|
||||
|
||||
Future<String> _getAppFlowyCloudWSUrl(String baseURL) async {
|
||||
@@ -339,27 +332,30 @@ Future<String> _getAppFlowyCloudGotrueUrl(String baseURL) async {
|
||||
return "$baseURL/gotrue";
|
||||
}
|
||||
|
||||
Future<void> setSupbaseServer(
|
||||
Option<String> url,
|
||||
Option<String> anonKey,
|
||||
Future<void> setSupabaseServer(
|
||||
String? url,
|
||||
String? anonKey,
|
||||
) async {
|
||||
assert(
|
||||
(url.isSome() && anonKey.isSome()) || (url.isNone() && anonKey.isNone()),
|
||||
(url != null && anonKey != null) || (url == null && anonKey == null),
|
||||
"Either both Supabase URL and anon key must be set, or both should be unset",
|
||||
);
|
||||
|
||||
await url.fold(
|
||||
() => getIt<KeyValueStorage>().remove(KVKeys.kSupabaseURL),
|
||||
(s) => getIt<KeyValueStorage>().set(KVKeys.kSupabaseURL, s),
|
||||
);
|
||||
await anonKey.fold(
|
||||
() => getIt<KeyValueStorage>().remove(KVKeys.kSupabaseAnonKey),
|
||||
(s) => getIt<KeyValueStorage>().set(KVKeys.kSupabaseAnonKey, s),
|
||||
);
|
||||
if (url == null) {
|
||||
await getIt<KeyValueStorage>().remove(KVKeys.kSupabaseURL);
|
||||
} else {
|
||||
await getIt<KeyValueStorage>().set(KVKeys.kSupabaseURL, url);
|
||||
}
|
||||
|
||||
if (anonKey == null) {
|
||||
await getIt<KeyValueStorage>().remove(KVKeys.kSupabaseAnonKey);
|
||||
} else {
|
||||
await getIt<KeyValueStorage>().set(KVKeys.kSupabaseAnonKey, anonKey);
|
||||
}
|
||||
}
|
||||
|
||||
Future<SupabaseConfiguration> getSupabaseCloudConfig() async {
|
||||
final url = await _getSupbaseUrl();
|
||||
final url = await _getSupabaseUrl();
|
||||
final anonKey = await _getSupabaseAnonKey();
|
||||
return SupabaseConfiguration(
|
||||
url: url,
|
||||
@@ -367,18 +363,12 @@ Future<SupabaseConfiguration> getSupabaseCloudConfig() async {
|
||||
);
|
||||
}
|
||||
|
||||
Future<String> _getSupbaseUrl() async {
|
||||
Future<String> _getSupabaseUrl() async {
|
||||
final result = await getIt<KeyValueStorage>().get(KVKeys.kSupabaseURL);
|
||||
return result.fold(
|
||||
() => "",
|
||||
(url) => url,
|
||||
);
|
||||
return result ?? '';
|
||||
}
|
||||
|
||||
Future<String> _getSupabaseAnonKey() async {
|
||||
final result = await getIt<KeyValueStorage>().get(KVKeys.kSupabaseAnonKey);
|
||||
return result.fold(
|
||||
() => "",
|
||||
(url) => url,
|
||||
);
|
||||
return result ?? '';
|
||||
}
|
||||
+1
-1
@@ -31,8 +31,8 @@ class UserProfileBloc extends Bloc<UserProfileEvent, UserProfileState> {
|
||||
);
|
||||
|
||||
final userProfile = userOrFailure.fold(
|
||||
(error) => null,
|
||||
(userProfilePB) => userProfilePB,
|
||||
(error) => null,
|
||||
);
|
||||
|
||||
if (workspaceSetting == null || userProfile == null) {
|
||||
|
||||
@@ -8,14 +8,17 @@ class AppBarBackButton extends StatelessWidget {
|
||||
const AppBarBackButton({
|
||||
super.key,
|
||||
this.onTap,
|
||||
this.padding,
|
||||
});
|
||||
|
||||
final VoidCallback? onTap;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppBarButton(
|
||||
onTap: onTap ?? () => Navigator.pop(context),
|
||||
padding: padding,
|
||||
child: const FlowySvg(
|
||||
FlowySvgs.m_app_bar_back_s,
|
||||
),
|
||||
@@ -73,10 +76,10 @@ class AppBarDoneButton extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppBarButton(
|
||||
isActionButton: true,
|
||||
onTap: onTap,
|
||||
padding: const EdgeInsets.fromLTRB(12, 12, 8, 12),
|
||||
child: FlowyText(
|
||||
LocaleKeys.button_Done.tr(),
|
||||
LocaleKeys.button_done.tr(),
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
fontWeight: FontWeight.w500,
|
||||
textAlign: TextAlign.right,
|
||||
@@ -85,6 +88,39 @@ class AppBarDoneButton extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class AppBarSaveButton extends StatelessWidget {
|
||||
const AppBarSaveButton({
|
||||
super.key,
|
||||
required this.onTap,
|
||||
this.enable = true,
|
||||
this.padding = const EdgeInsets.fromLTRB(12, 12, 8, 12),
|
||||
});
|
||||
|
||||
final VoidCallback onTap;
|
||||
final bool enable;
|
||||
final EdgeInsetsGeometry padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppBarButton(
|
||||
onTap: () {
|
||||
if (enable) {
|
||||
onTap();
|
||||
}
|
||||
},
|
||||
padding: padding,
|
||||
child: FlowyText(
|
||||
LocaleKeys.button_save.tr(),
|
||||
color: enable
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).disabledColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
textAlign: TextAlign.right,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AppBarFilledDoneButton extends StatelessWidget {
|
||||
const AppBarFilledDoneButton({super.key, required this.onTap});
|
||||
|
||||
@@ -129,7 +165,7 @@ class AppBarMoreButton extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppBarButton(
|
||||
isActionButton: true,
|
||||
padding: const EdgeInsets.fromLTRB(12, 12, 8, 12),
|
||||
onTap: () => onTap(context),
|
||||
child: const FlowySvg(FlowySvgs.three_dots_s),
|
||||
);
|
||||
@@ -139,14 +175,14 @@ class AppBarMoreButton extends StatelessWidget {
|
||||
class AppBarButton extends StatelessWidget {
|
||||
const AppBarButton({
|
||||
super.key,
|
||||
this.isActionButton = false,
|
||||
required this.onTap,
|
||||
required this.child,
|
||||
this.padding,
|
||||
});
|
||||
|
||||
final VoidCallback onTap;
|
||||
final Widget child;
|
||||
final bool isActionButton;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -154,12 +190,7 @@ class AppBarButton extends StatelessWidget {
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 12.0,
|
||||
bottom: 12.0,
|
||||
left: 12.0,
|
||||
right: isActionButton ? 12.0 : 8.0,
|
||||
),
|
||||
padding: padding ?? const EdgeInsets.all(12),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -13,7 +13,7 @@ import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_service.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:dartz/dartz.dart' hide State;
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -40,7 +40,7 @@ class MobileViewPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MobileViewPageState extends State<MobileViewPage> {
|
||||
late final Future<Either<ViewPB, FlowyError>> future;
|
||||
late final Future<FlowyResult<ViewPB, FlowyError>> future;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -71,10 +71,9 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
||||
body = state.data!.fold((view) {
|
||||
viewPB = view;
|
||||
actions.add(_buildAppBarMoreButton(view));
|
||||
return view
|
||||
.plugin(arguments: widget.arguments ?? const {})
|
||||
.widgetBuilder
|
||||
.buildWidget(shrinkWrap: false);
|
||||
final plugin = view.plugin(arguments: widget.arguments ?? const {})
|
||||
..init();
|
||||
return plugin.widgetBuilder.buildWidget(shrinkWrap: false);
|
||||
}, (error) {
|
||||
return FlowyMobileStateContainer.error(
|
||||
emoji: '😔',
|
||||
|
||||
@@ -7,3 +7,4 @@ export 'bottom_sheet_view_item_body.dart';
|
||||
export 'bottom_sheet_view_page.dart';
|
||||
export 'default_mobile_action_pane.dart';
|
||||
export 'show_mobile_bottom_sheet.dart';
|
||||
export 'show_transition_bottom_sheet.dart';
|
||||
+1
-1
@@ -49,7 +49,7 @@ class BottomSheetHeader extends StatelessWidget {
|
||||
color: Color(0xFF00BCF0),
|
||||
),
|
||||
text: FlowyText.medium(
|
||||
LocaleKeys.button_Done.tr(),
|
||||
LocaleKeys.button_done.tr(),
|
||||
color: Colors.white,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
|
||||
+39
-34
@@ -1,9 +1,27 @@
|
||||
import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart';
|
||||
import 'package:appflowy/plugins/base/drag_handler.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart' hide WidgetBuilder;
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
extension BottomSheetPaddingExtension on BuildContext {
|
||||
/// Calculates the total amount of space that should be added to the bottom of
|
||||
/// a bottom sheet
|
||||
double bottomSheetPadding({
|
||||
bool ignoreViewPadding = true,
|
||||
}) {
|
||||
final viewPadding = MediaQuery.viewPaddingOf(this);
|
||||
final viewInsets = MediaQuery.viewInsetsOf(this);
|
||||
double bottom = 0.0;
|
||||
if (!ignoreViewPadding) {
|
||||
bottom += viewPadding.bottom;
|
||||
}
|
||||
// for screens with 0 view padding, add some even more space
|
||||
bottom += viewPadding.bottom == 0 ? 28.0 : 16.0;
|
||||
bottom += viewInsets.bottom;
|
||||
return bottom;
|
||||
}
|
||||
}
|
||||
|
||||
Future<T?> showMobileBottomSheet<T>(
|
||||
BuildContext context, {
|
||||
required WidgetBuilder builder,
|
||||
@@ -16,7 +34,6 @@ Future<T?> showMobileBottomSheet<T>(
|
||||
bool showCloseButton = false,
|
||||
// this field is only used if showHeader is true
|
||||
String title = '',
|
||||
bool resizeToAvoidBottomInset = true,
|
||||
bool isScrollControlled = true,
|
||||
bool showDivider = true,
|
||||
bool useRootNavigator = false,
|
||||
@@ -42,7 +59,7 @@ Future<T?> showMobileBottomSheet<T>(
|
||||
|
||||
shape ??= const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Corners.s12Radius,
|
||||
top: Radius.circular(16),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -68,23 +85,20 @@ Future<T?> showMobileBottomSheet<T>(
|
||||
final Widget child = builder(context);
|
||||
|
||||
// if the children is only one, we don't need to wrap it with a column
|
||||
if (!showDragHandle &&
|
||||
!showHeader &&
|
||||
!showDivider &&
|
||||
!resizeToAvoidBottomInset) {
|
||||
if (!showDragHandle && !showHeader && !showDivider) {
|
||||
return child;
|
||||
}
|
||||
|
||||
// ----- header area -----
|
||||
if (showDragHandle) {
|
||||
children.add(
|
||||
const DragHandler(),
|
||||
const DragHandle(),
|
||||
);
|
||||
}
|
||||
|
||||
if (showHeader) {
|
||||
children.add(
|
||||
_Header(
|
||||
BottomSheetHeader(
|
||||
showCloseButton: showCloseButton,
|
||||
showBackButton: showBackButton,
|
||||
showDoneButton: showDoneButton,
|
||||
@@ -113,9 +127,11 @@ Future<T?> showMobileBottomSheet<T>(
|
||||
children: [
|
||||
...children,
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
child: child,
|
||||
child: Scrollbar(
|
||||
child: SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -125,32 +141,20 @@ Future<T?> showMobileBottomSheet<T>(
|
||||
}
|
||||
|
||||
// ----- content area -----
|
||||
if (resizeToAvoidBottomInset) {
|
||||
children.add(
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: padding.top,
|
||||
left: padding.left,
|
||||
right: padding.right,
|
||||
bottom: padding.bottom + MediaQuery.of(context).viewInsets.bottom,
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
children.add(child);
|
||||
}
|
||||
// add content padding and extra bottom padding
|
||||
children.add(
|
||||
Padding(
|
||||
padding:
|
||||
padding + EdgeInsets.only(bottom: context.bottomSheetPadding()),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
// ----- content area -----
|
||||
|
||||
if (children.length == 1) {
|
||||
return children.first;
|
||||
}
|
||||
|
||||
// add default padding
|
||||
children.add(
|
||||
VSpace(MediaQuery.of(context).padding.bottom == 0 ? 28.0 : 16.0),
|
||||
);
|
||||
|
||||
return useSafeArea
|
||||
? SafeArea(
|
||||
child: Column(
|
||||
@@ -166,8 +170,9 @@ Future<T?> showMobileBottomSheet<T>(
|
||||
);
|
||||
}
|
||||
|
||||
class _Header extends StatelessWidget {
|
||||
const _Header({
|
||||
class BottomSheetHeader extends StatelessWidget {
|
||||
const BottomSheetHeader({
|
||||
super.key,
|
||||
required this.showBackButton,
|
||||
required this.showCloseButton,
|
||||
required this.title,
|
||||
|
||||
+345
@@ -0,0 +1,345 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:appflowy/plugins/base/drag_handler.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:sheet/route.dart';
|
||||
import 'package:sheet/sheet.dart';
|
||||
|
||||
import 'show_mobile_bottom_sheet.dart';
|
||||
|
||||
Future<T?> showTransitionMobileBottomSheet<T>(
|
||||
BuildContext context, {
|
||||
required WidgetBuilder builder,
|
||||
bool useRootNavigator = false,
|
||||
EdgeInsets contentPadding = EdgeInsets.zero,
|
||||
Color? backgroundColor,
|
||||
// drag handle
|
||||
bool showDragHandle = false,
|
||||
// header
|
||||
bool showHeader = false,
|
||||
String title = '',
|
||||
bool showBackButton = false,
|
||||
bool showCloseButton = false,
|
||||
bool showDoneButton = false,
|
||||
bool showDivider = true,
|
||||
// stops
|
||||
double initialStop = 1.0,
|
||||
List<double>? stops,
|
||||
}) {
|
||||
assert(
|
||||
showHeader ||
|
||||
title.isEmpty &&
|
||||
!showCloseButton &&
|
||||
!showBackButton &&
|
||||
!showDoneButton &&
|
||||
!showDivider,
|
||||
);
|
||||
assert(!(showCloseButton && showBackButton));
|
||||
|
||||
backgroundColor ??= Theme.of(context).brightness == Brightness.light
|
||||
? const Color(0xFFF7F8FB)
|
||||
: const Color(0xFF626364);
|
||||
|
||||
return Navigator.of(
|
||||
context,
|
||||
rootNavigator: useRootNavigator,
|
||||
).push<T>(
|
||||
TransitionSheetRoute<T>(
|
||||
backgroundColor: backgroundColor,
|
||||
initialStop: initialStop,
|
||||
stops: stops,
|
||||
builder: (context) {
|
||||
final Widget child = builder(context);
|
||||
|
||||
// if the children is only one, we don't need to wrap it with a column
|
||||
if (!showDragHandle && !showHeader) {
|
||||
return child;
|
||||
}
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (showDragHandle) const DragHandle(),
|
||||
if (showHeader) ...[
|
||||
BottomSheetHeader(
|
||||
showCloseButton: showCloseButton,
|
||||
showBackButton: showBackButton,
|
||||
showDoneButton: showDoneButton,
|
||||
title: title,
|
||||
),
|
||||
if (showDivider)
|
||||
const Divider(
|
||||
height: 0.5,
|
||||
thickness: 0.5,
|
||||
),
|
||||
],
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: contentPadding,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// The top offset that will be displayed from the bottom route
|
||||
const double _kPreviousRouteVisibleOffset = 10.0;
|
||||
|
||||
/// Minimal distance from the top of the screen to the top of the previous route
|
||||
/// It will be used ff the top safe area is less than this value.
|
||||
/// In iPhones the top SafeArea is more or equal to this distance.
|
||||
const double _kSheetMinimalOffset = 10;
|
||||
|
||||
const Curve _kCupertinoSheetCurve = Curves.easeOutExpo;
|
||||
const Curve _kCupertinoTransitionCurve = Curves.linear;
|
||||
|
||||
/// Wraps the child into a cupertino modal sheet appearance. This is used to
|
||||
/// create a [SheetRoute].
|
||||
///
|
||||
/// Clip the child widget to rectangle with top rounded corners and adds
|
||||
/// top padding and top safe area.
|
||||
class _CupertinoSheetDecorationBuilder extends StatelessWidget {
|
||||
const _CupertinoSheetDecorationBuilder({
|
||||
required this.child,
|
||||
required this.topRadius,
|
||||
this.backgroundColor,
|
||||
});
|
||||
|
||||
/// The child contained by the modal sheet
|
||||
final Widget child;
|
||||
|
||||
/// The color to paint behind the child
|
||||
final Color? backgroundColor;
|
||||
|
||||
/// The top corners of this modal sheet are rounded by this Radius
|
||||
final Radius topRadius;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Builder(
|
||||
builder: (BuildContext context) {
|
||||
return Container(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.vertical(top: topRadius),
|
||||
color: backgroundColor,
|
||||
),
|
||||
child: MediaQuery.removePadding(
|
||||
context: context,
|
||||
removeTop: true,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Customized CupertinoSheetRoute from the sheets package
|
||||
///
|
||||
/// A modal route that overlays a widget over the current route and animates
|
||||
/// it from the bottom with a cupertino modal sheet appearance
|
||||
///
|
||||
/// Clip the child widget to rectangle with top rounded corners and adds
|
||||
/// top padding and top safe area.
|
||||
class TransitionSheetRoute<T> extends SheetRoute<T> {
|
||||
TransitionSheetRoute({
|
||||
required WidgetBuilder builder,
|
||||
super.stops,
|
||||
double initialStop = 1.0,
|
||||
super.settings,
|
||||
Color? backgroundColor,
|
||||
super.maintainState = true,
|
||||
super.fit,
|
||||
}) : super(
|
||||
builder: (BuildContext context) {
|
||||
return _CupertinoSheetDecorationBuilder(
|
||||
backgroundColor: backgroundColor,
|
||||
topRadius: const Radius.circular(16),
|
||||
child: Builder(builder: builder),
|
||||
);
|
||||
},
|
||||
animationCurve: _kCupertinoSheetCurve,
|
||||
initialExtent: initialStop,
|
||||
);
|
||||
|
||||
@override
|
||||
bool get draggable => true;
|
||||
|
||||
final SheetController _sheetController = SheetController();
|
||||
|
||||
@override
|
||||
SheetController createSheetController() => _sheetController;
|
||||
|
||||
@override
|
||||
Color? get barrierColor => Colors.transparent;
|
||||
|
||||
@override
|
||||
bool get barrierDismissible => true;
|
||||
|
||||
@override
|
||||
Widget buildSheet(BuildContext context, Widget child) {
|
||||
final effectivePhysics = draggable
|
||||
? BouncingSheetPhysics(
|
||||
parent: SnapSheetPhysics(
|
||||
stops: stops ?? <double>[0, 1],
|
||||
parent: physics,
|
||||
),
|
||||
)
|
||||
: const NeverDraggableSheetPhysics();
|
||||
final MediaQueryData mediaQuery = MediaQuery.of(context);
|
||||
final double topMargin =
|
||||
math.max(_kSheetMinimalOffset, mediaQuery.padding.top) +
|
||||
_kPreviousRouteVisibleOffset;
|
||||
return Sheet.raw(
|
||||
initialExtent: initialExtent,
|
||||
decorationBuilder: decorationBuilder,
|
||||
fit: fit,
|
||||
maxExtent: mediaQuery.size.height - topMargin,
|
||||
physics: effectivePhysics,
|
||||
controller: sheetController,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildTransitions(
|
||||
BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
Widget child,
|
||||
) {
|
||||
final double topPadding = MediaQuery.of(context).padding.top;
|
||||
final double topOffset = math.max(_kSheetMinimalOffset, topPadding);
|
||||
return AnimatedBuilder(
|
||||
animation: secondaryAnimation,
|
||||
child: child,
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
final double progress = secondaryAnimation.value;
|
||||
final double scale = 1 - progress / 10;
|
||||
final double distanceWithScale =
|
||||
(topOffset + _kPreviousRouteVisibleOffset) * 0.9;
|
||||
final Offset offset =
|
||||
Offset(0, progress * (topOffset - distanceWithScale));
|
||||
return Transform.translate(
|
||||
offset: offset,
|
||||
child: Transform.scale(
|
||||
scale: scale,
|
||||
alignment: Alignment.topCenter,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool canDriveSecondaryTransitionForPreviousRoute(
|
||||
Route<dynamic> previousRoute,
|
||||
) =>
|
||||
true;
|
||||
|
||||
@override
|
||||
Widget buildSecondaryTransitionForPreviousRoute(
|
||||
BuildContext context,
|
||||
Animation<double> secondaryAnimation,
|
||||
Widget child,
|
||||
) {
|
||||
final Animation<double> delayAnimation = CurvedAnimation(
|
||||
parent: _sheetController.animation,
|
||||
curve: Interval(
|
||||
initialExtent == 1 ? 0 : initialExtent,
|
||||
1,
|
||||
),
|
||||
);
|
||||
|
||||
final Animation<double> secondaryAnimation = CurvedAnimation(
|
||||
parent: _sheetController.animation,
|
||||
curve: Interval(
|
||||
0,
|
||||
initialExtent,
|
||||
),
|
||||
);
|
||||
|
||||
return CupertinoSheetBottomRouteTransition(
|
||||
body: child,
|
||||
sheetAnimation: delayAnimation,
|
||||
secondaryAnimation: secondaryAnimation,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Animation for previous route when a [TransitionSheetRoute] enters/exits
|
||||
@visibleForTesting
|
||||
class CupertinoSheetBottomRouteTransition extends StatelessWidget {
|
||||
const CupertinoSheetBottomRouteTransition({
|
||||
super.key,
|
||||
required this.sheetAnimation,
|
||||
required this.secondaryAnimation,
|
||||
required this.body,
|
||||
});
|
||||
|
||||
final Widget body;
|
||||
|
||||
final Animation<double> sheetAnimation;
|
||||
final Animation<double> secondaryAnimation;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final double topPadding = MediaQuery.of(context).padding.top;
|
||||
final double topOffset = math.max(_kSheetMinimalOffset, topPadding);
|
||||
|
||||
final CurvedAnimation curvedAnimation = CurvedAnimation(
|
||||
parent: sheetAnimation,
|
||||
curve: _kCupertinoTransitionCurve,
|
||||
);
|
||||
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle.light,
|
||||
child: AnimatedBuilder(
|
||||
animation: secondaryAnimation,
|
||||
child: body,
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
final double progress = curvedAnimation.value;
|
||||
final double scale = 1 - progress / 10;
|
||||
return Stack(
|
||||
children: <Widget>[
|
||||
Container(color: Colors.black),
|
||||
Transform.translate(
|
||||
offset: Offset(0, progress * topOffset),
|
||||
child: Transform.scale(
|
||||
scale: scale,
|
||||
alignment: Alignment.topCenter,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.lerp(
|
||||
Radius.zero,
|
||||
const Radius.circular(16.0),
|
||||
progress,
|
||||
)!,
|
||||
),
|
||||
child: ColorFiltered(
|
||||
colorFilter: ColorFilter.mode(
|
||||
(Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.grey
|
||||
: Colors.black)
|
||||
.withOpacity(secondaryAnimation.value * 0.1),
|
||||
BlendMode.srcOver,
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+40
-41
@@ -1,6 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart';
|
||||
import 'package:appflowy/plugins/database/board/application/board_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/header/field_type_extension.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
|
||||
@@ -8,7 +11,6 @@ import 'package:appflowy_backend/protobuf/flowy-database2/group.pb.dart';
|
||||
import 'package:appflowy_board/appflowy_board.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
@@ -107,46 +109,43 @@ class _GroupCardHeaderState extends State<GroupCardHeader> {
|
||||
splashRadius: 5,
|
||||
onPressed: () => showMobileBottomSheet(
|
||||
context,
|
||||
title: LocaleKeys.board_column_groupActions.tr(),
|
||||
showHeader: true,
|
||||
showCloseButton: true,
|
||||
builder: (_) {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: BottomSheetActionWidget(
|
||||
svg: FlowySvgs.edit_s,
|
||||
text: LocaleKeys.board_column_renameColumn.tr(),
|
||||
onTap: () {
|
||||
context.read<BoardBloc>().add(
|
||||
BoardEvent.startEditingHeader(
|
||||
widget.groupData.id,
|
||||
),
|
||||
);
|
||||
context.pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
const HSpace(8),
|
||||
Expanded(
|
||||
child: BottomSheetActionWidget(
|
||||
svg: FlowySvgs.hide_s,
|
||||
text: LocaleKeys.board_column_hideColumn.tr(),
|
||||
onTap: () {
|
||||
context.read<BoardBloc>().add(
|
||||
BoardEvent.toggleGroupVisibility(
|
||||
widget.groupData.customData.group
|
||||
as GroupPB,
|
||||
false,
|
||||
),
|
||||
);
|
||||
context.pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
showDragHandle: true,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
builder: (_) => SeparatedColumn(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
separatorBuilder: () => const Divider(
|
||||
height: 8.5,
|
||||
thickness: 0.5,
|
||||
),
|
||||
children: [
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.board_column_renameColumn.tr(),
|
||||
icon: FlowySvgs.edit_s,
|
||||
onTap: () {
|
||||
context.read<BoardBloc>().add(
|
||||
BoardEvent.startEditingHeader(
|
||||
widget.groupData.id,
|
||||
),
|
||||
);
|
||||
context.pop();
|
||||
},
|
||||
),
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.board_column_hideColumn.tr(),
|
||||
icon: FlowySvgs.hide_s,
|
||||
onTap: () {
|
||||
context.read<BoardBloc>().add(
|
||||
BoardEvent.toggleGroupVisibility(
|
||||
widget.groupData.customData.group
|
||||
as GroupPB,
|
||||
false,
|
||||
),
|
||||
);
|
||||
context.pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ class MobileRowDetailCreateFieldButton extends StatelessWidget {
|
||||
LocaleKeys.grid_field_newProperty.tr(),
|
||||
fontSize: 15,
|
||||
),
|
||||
onPressed: () => showCreateFieldBottomSheet(context, viewId),
|
||||
onPressed: () => mobileCreateFieldWorkflow(context, viewId),
|
||||
icon: const FlowySvg(FlowySvgs.add_m),
|
||||
),
|
||||
);
|
||||
|
||||
+28
-13
@@ -1,7 +1,10 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/database/field/mobile_field_bottom_sheets.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/util/field_type_extension.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class OptionTextField extends StatelessWidget {
|
||||
@@ -10,31 +13,43 @@ class OptionTextField extends StatelessWidget {
|
||||
required this.controller,
|
||||
required this.type,
|
||||
required this.onTextChanged,
|
||||
required this.onFieldTypeChanged,
|
||||
});
|
||||
|
||||
final TextEditingController controller;
|
||||
final FieldType type;
|
||||
final void Function(String value) onTextChanged;
|
||||
final void Function(FieldType value) onFieldTypeChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FlowyOptionTile.textField(
|
||||
controller: controller,
|
||||
autofocus: true,
|
||||
textFieldPadding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
onTextChanged: onTextChanged,
|
||||
leftIcon: Container(
|
||||
height: 38,
|
||||
width: 38,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: type.mobileIconBackgroundColor,
|
||||
),
|
||||
child: Center(
|
||||
child: FlowySvg(
|
||||
type.svgData,
|
||||
blendMode: null,
|
||||
size: const Size.square(22),
|
||||
leftIcon: GestureDetector(
|
||||
onTap: () async {
|
||||
final fieldType = await showFieldTypeGridBottomSheet(
|
||||
context,
|
||||
title: LocaleKeys.grid_field_editProperty.tr(),
|
||||
);
|
||||
if (fieldType != null) {
|
||||
onFieldTypeChanged(fieldType);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
height: 38,
|
||||
width: 38,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: type.mobileIconBackgroundColor,
|
||||
),
|
||||
child: Center(
|
||||
child: FlowySvg(
|
||||
type.svgData,
|
||||
blendMode: null,
|
||||
size: const Size.square(22),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ class _MobileDateCellEditScreenState extends State<MobileDateCellEditScreen> {
|
||||
children: [
|
||||
ColoredBox(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: const Center(child: DragHandler()),
|
||||
child: const Center(child: DragHandle()),
|
||||
),
|
||||
const MobileDateHeader(),
|
||||
_buildDatePicker(),
|
||||
|
||||
+21
-5
@@ -1,9 +1,9 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/app_bar.dart';
|
||||
import 'package:appflowy/mobile/presentation/database/field/mobile_full_field_editor.dart';
|
||||
import 'package:appflowy/plugins/database/application/field/field_backend_service.dart';
|
||||
import 'package:appflowy/plugins/database/domain/field_backend_service.dart';
|
||||
import 'package:appflowy/plugins/database/application/field/field_info.dart';
|
||||
import 'package:appflowy/plugins/database/application/field/field_service.dart';
|
||||
import 'package:appflowy/plugins/database/domain/field_service.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/setting/field_visibility_extension.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -61,7 +61,7 @@ class _MobileEditPropertyScreenState extends State<MobileEditPropertyScreen> {
|
||||
body: MobileFieldEditor(
|
||||
mode: FieldOptionMode.edit,
|
||||
isPrimary: widget.field.isPrimary,
|
||||
defaultValues: _fieldOptionValues,
|
||||
defaultValues: FieldOptionValues.fromField(field: widget.field.field),
|
||||
actions: [
|
||||
widget.field.fieldSettings?.visibility.isVisibleState() ?? true
|
||||
? FieldOptionAction.hide
|
||||
@@ -69,9 +69,25 @@ class _MobileEditPropertyScreenState extends State<MobileEditPropertyScreen> {
|
||||
FieldOptionAction.duplicate,
|
||||
FieldOptionAction.delete,
|
||||
],
|
||||
onOptionValuesChanged: (newFieldOptionValues) {
|
||||
onOptionValuesChanged: (fieldOptionValues) async {
|
||||
await fieldService.updateField(name: fieldOptionValues.name);
|
||||
|
||||
await FieldBackendService.updateFieldType(
|
||||
viewId: widget.viewId,
|
||||
fieldId: widget.field.id,
|
||||
fieldType: fieldOptionValues.type,
|
||||
);
|
||||
|
||||
final data = fieldOptionValues.getTypeOptionData();
|
||||
if (data != null) {
|
||||
await FieldBackendService.updateFieldTypeOption(
|
||||
viewId: widget.viewId,
|
||||
fieldId: widget.field.id,
|
||||
typeOptionData: data,
|
||||
);
|
||||
}
|
||||
setState(() {
|
||||
_fieldOptionValues = newFieldOptionValues;
|
||||
_fieldOptionValues = fieldOptionValues;
|
||||
});
|
||||
},
|
||||
onAction: (action) {
|
||||
|
||||
+40
-30
@@ -29,23 +29,18 @@ const mobileSupportedFieldTypes = [
|
||||
FieldType.Checklist,
|
||||
];
|
||||
|
||||
/// Shows the field type grid and upon selection, allow users to edit the
|
||||
/// field's properties and saving it when the user clicks save.
|
||||
void showCreateFieldBottomSheet(
|
||||
BuildContext context,
|
||||
String viewId, {
|
||||
OrderObjectPositionPB? position,
|
||||
Future<FieldType?> showFieldTypeGridBottomSheet(
|
||||
BuildContext context, {
|
||||
required String title,
|
||||
}) {
|
||||
showMobileBottomSheet(
|
||||
return showMobileBottomSheet<FieldType>(
|
||||
context,
|
||||
padding: EdgeInsets.zero,
|
||||
showHeader: true,
|
||||
showDragHandle: true,
|
||||
showCloseButton: true,
|
||||
elevation: 20,
|
||||
title: LocaleKeys.grid_field_newProperty.tr(),
|
||||
title: title,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
barrierColor: Colors.transparent,
|
||||
enableDraggableScrollable: true,
|
||||
builder: (context) {
|
||||
final typeOptionMenuItemValue = mobileSupportedFieldTypes
|
||||
@@ -55,24 +50,8 @@ void showCreateFieldBottomSheet(
|
||||
backgroundColor: fieldType.mobileIconBackgroundColor,
|
||||
text: fieldType.i18n,
|
||||
icon: fieldType.svgData,
|
||||
onTap: (_, fieldType) async {
|
||||
final optionValues = await context.push<FieldOptionValues>(
|
||||
Uri(
|
||||
path: MobileNewPropertyScreen.routeName,
|
||||
queryParameters: {
|
||||
MobileNewPropertyScreen.argViewId: viewId,
|
||||
MobileNewPropertyScreen.argFieldTypeId:
|
||||
fieldType.value.toString(),
|
||||
},
|
||||
).toString(),
|
||||
);
|
||||
if (optionValues != null) {
|
||||
await optionValues.create(viewId: viewId, position: position);
|
||||
if (context.mounted) {
|
||||
context.pop();
|
||||
}
|
||||
}
|
||||
},
|
||||
onTap: (context, fieldType) =>
|
||||
Navigator.of(context).pop(fieldType),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
@@ -87,6 +66,37 @@ void showCreateFieldBottomSheet(
|
||||
);
|
||||
}
|
||||
|
||||
/// Shows the field type grid and upon selection, allow users to edit the
|
||||
/// field's properties and saving it when the user clicks save.
|
||||
void mobileCreateFieldWorkflow(
|
||||
BuildContext context,
|
||||
String viewId, {
|
||||
OrderObjectPositionPB? position,
|
||||
}) async {
|
||||
final fieldType = await showFieldTypeGridBottomSheet(
|
||||
context,
|
||||
title: LocaleKeys.grid_field_newProperty.tr(),
|
||||
);
|
||||
if (fieldType == null || !context.mounted) {
|
||||
return;
|
||||
}
|
||||
final optionValues = await context.push<FieldOptionValues>(
|
||||
Uri(
|
||||
path: MobileNewPropertyScreen.routeName,
|
||||
queryParameters: {
|
||||
MobileNewPropertyScreen.argViewId: viewId,
|
||||
MobileNewPropertyScreen.argFieldTypeId: fieldType.value.toString(),
|
||||
},
|
||||
).toString(),
|
||||
);
|
||||
if (optionValues != null) {
|
||||
await optionValues.create(viewId: viewId, position: position);
|
||||
}
|
||||
if (context.mounted) {
|
||||
context.pop();
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to edit a field.
|
||||
Future<FieldOptionValues?> showEditFieldScreen(
|
||||
BuildContext context,
|
||||
@@ -106,18 +116,18 @@ Future<FieldOptionValues?> showEditFieldScreen(
|
||||
void showQuickEditField(
|
||||
BuildContext context,
|
||||
String viewId,
|
||||
FieldController fieldController,
|
||||
FieldInfo fieldInfo,
|
||||
) {
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
padding: EdgeInsets.zero,
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
resizeToAvoidBottomInset: true,
|
||||
showDragHandle: true,
|
||||
builder: (context) {
|
||||
return SingleChildScrollView(
|
||||
child: QuickEditField(
|
||||
viewId: viewId,
|
||||
fieldController: fieldController,
|
||||
fieldInfo: fieldInfo,
|
||||
),
|
||||
);
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ class _MobileFieldPickerListState extends State<MobileFieldPickerList> {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const DragHandler(),
|
||||
const DragHandle(),
|
||||
_Header(
|
||||
title: widget.title,
|
||||
onDone: (context) => context.pop(newFieldId),
|
||||
|
||||
+19
-37
@@ -5,16 +5,14 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/flowy_search_text_field.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/option_color_list.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/type_option_menu_item.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/widgets.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/plugins/base/drag_handler.dart';
|
||||
import 'package:appflowy/plugins/database/application/field/field_service.dart';
|
||||
import 'package:appflowy/plugins/database/domain/field_service.dart';
|
||||
import 'package:appflowy/plugins/database/application/field/type_option/number_format_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/header/type_option/date/date_time_format.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell_editor/extension.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy/util/field_type_extension.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
@@ -223,6 +221,18 @@ class _MobileFieldEditorState extends State<MobileFieldEditor> {
|
||||
isFieldNameChanged = true;
|
||||
_updateOptionValues(name: value);
|
||||
},
|
||||
onFieldTypeChanged: (type) {
|
||||
setState(
|
||||
() {
|
||||
if (widget.mode == FieldOptionMode.add &&
|
||||
!isFieldNameChanged) {
|
||||
controller.text = type.i18n;
|
||||
_updateOptionValues(name: type.i18n);
|
||||
}
|
||||
_updateOptionValues(type: type);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
const _Divider(),
|
||||
if (!widget.isPrimary) ...[
|
||||
@@ -444,42 +454,14 @@ class _PropertyType extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
showMobileBottomSheet(
|
||||
onTap: () async {
|
||||
final fieldType = await showFieldTypeGridBottomSheet(
|
||||
context,
|
||||
padding: EdgeInsets.zero,
|
||||
showHeader: true,
|
||||
showDragHandle: true,
|
||||
showCloseButton: true,
|
||||
elevation: 20,
|
||||
title: LocaleKeys.grid_field_editProperty.tr(),
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
barrierColor: Colors.transparent,
|
||||
enableDraggableScrollable: true,
|
||||
builder: (context) {
|
||||
final typeOptionMenuItemValue = mobileSupportedFieldTypes
|
||||
.map(
|
||||
(fieldType) => TypeOptionMenuItemValue(
|
||||
value: fieldType,
|
||||
backgroundColor: fieldType.mobileIconBackgroundColor,
|
||||
text: fieldType.i18n,
|
||||
icon: fieldType.svgData,
|
||||
onTap: (_, fieldType) {
|
||||
onSelected(fieldType);
|
||||
context.pop();
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
return Padding(
|
||||
padding: EdgeInsets.all(16 * context.scale),
|
||||
child: TypeOptionMenu<FieldType>(
|
||||
values: typeOptionMenuItemValue,
|
||||
scaleFactor: context.scale,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
if (fieldType != null) {
|
||||
onSelected(fieldType);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -711,7 +693,7 @@ class _NumberFormatListState extends State<_NumberFormatList> {
|
||||
controller: widget.scrollController,
|
||||
children: [
|
||||
const Center(
|
||||
child: DragHandler(),
|
||||
child: DragHandle(),
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
|
||||
|
||||
+124
-141
@@ -2,27 +2,29 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/widgets.dart';
|
||||
import 'package:appflowy/mobile/presentation/database/field/mobile_field_bottom_sheets.dart';
|
||||
import 'package:appflowy/mobile/presentation/database/field/mobile_full_field_editor.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/plugins/database/application/field/field_backend_service.dart';
|
||||
import 'package:appflowy/plugins/database/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database/application/field/field_editor_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/domain/field_backend_service.dart';
|
||||
import 'package:appflowy/plugins/database/application/field/field_info.dart';
|
||||
import 'package:appflowy/plugins/database/application/field/field_service.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/setting/field_visibility_extension.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:protobuf/protobuf.dart' hide FieldInfo;
|
||||
|
||||
class QuickEditField extends StatefulWidget {
|
||||
const QuickEditField({
|
||||
super.key,
|
||||
required this.viewId,
|
||||
required this.fieldController,
|
||||
required this.fieldInfo,
|
||||
});
|
||||
|
||||
final String viewId;
|
||||
final FieldController fieldController;
|
||||
final FieldInfo fieldInfo;
|
||||
|
||||
@override
|
||||
@@ -38,14 +40,10 @@ class _QuickEditFieldState extends State<QuickEditField> {
|
||||
);
|
||||
|
||||
late FieldVisibility fieldVisibility;
|
||||
late FieldOptionValues _fieldOptionValues;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_fieldOptionValues =
|
||||
FieldOptionValues.fromField(field: widget.fieldInfo.field);
|
||||
fieldVisibility = widget.fieldInfo.fieldSettings?.visibility ??
|
||||
FieldVisibility.AlwaysShown;
|
||||
controller.text = widget.fieldInfo.field.name;
|
||||
@@ -59,140 +57,125 @@ class _QuickEditFieldState extends State<QuickEditField> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const VSpace(16),
|
||||
OptionTextField(
|
||||
controller: controller,
|
||||
type: _fieldOptionValues.type,
|
||||
onTextChanged: (text) async {
|
||||
await service.updateName(text);
|
||||
},
|
||||
),
|
||||
const _Divider(),
|
||||
FlowyOptionTile.text(
|
||||
text: LocaleKeys.grid_field_editProperty.tr(),
|
||||
leftIcon: const FlowySvg(FlowySvgs.m_field_edit_s),
|
||||
onTap: () async {
|
||||
widget.fieldInfo.field.freeze();
|
||||
final field = widget.fieldInfo.field.rebuild((field) {
|
||||
field.name = controller.text;
|
||||
field.fieldType = _fieldOptionValues.type;
|
||||
field.typeOptionData =
|
||||
_fieldOptionValues.getTypeOptionData() ?? [];
|
||||
});
|
||||
final fieldOptionValues = await showEditFieldScreen(
|
||||
context,
|
||||
widget.viewId,
|
||||
widget.fieldInfo.copyWith(field: field),
|
||||
);
|
||||
if (fieldOptionValues != null) {
|
||||
if (fieldOptionValues.name != _fieldOptionValues.name) {
|
||||
await service.updateName(fieldOptionValues.name);
|
||||
}
|
||||
|
||||
if (fieldOptionValues.type != _fieldOptionValues.type) {
|
||||
await FieldBackendService.updateFieldType(
|
||||
viewId: widget.viewId,
|
||||
fieldId: widget.fieldInfo.id,
|
||||
fieldType: fieldOptionValues.type,
|
||||
);
|
||||
}
|
||||
|
||||
final data = fieldOptionValues.getTypeOptionData();
|
||||
if (data != null) {
|
||||
await FieldBackendService.updateFieldTypeOption(
|
||||
viewId: widget.viewId,
|
||||
fieldId: widget.fieldInfo.id,
|
||||
typeOptionData: data,
|
||||
);
|
||||
}
|
||||
setState(() {
|
||||
_fieldOptionValues = fieldOptionValues;
|
||||
controller.text = fieldOptionValues.name;
|
||||
});
|
||||
} else {
|
||||
if (mounted) {
|
||||
context.pop();
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
if (!widget.fieldInfo.isPrimary)
|
||||
FlowyOptionTile.text(
|
||||
showTopBorder: false,
|
||||
text: fieldVisibility.isVisibleState()
|
||||
? LocaleKeys.grid_field_hide.tr()
|
||||
: LocaleKeys.grid_field_show.tr(),
|
||||
leftIcon: const FlowySvg(FlowySvgs.m_field_hide_s),
|
||||
onTap: () async {
|
||||
context.pop();
|
||||
if (fieldVisibility.isVisibleState()) {
|
||||
await service.hide();
|
||||
} else {
|
||||
await service.hide();
|
||||
}
|
||||
},
|
||||
),
|
||||
if (!widget.fieldInfo.isPrimary)
|
||||
FlowyOptionTile.text(
|
||||
showTopBorder: false,
|
||||
text: LocaleKeys.grid_field_insertLeft.tr(),
|
||||
leftIcon: const FlowySvg(FlowySvgs.m_filed_insert_left_s),
|
||||
onTap: () async {
|
||||
context.pop();
|
||||
showCreateFieldBottomSheet(
|
||||
context,
|
||||
widget.viewId,
|
||||
position: OrderObjectPositionPB(
|
||||
position: OrderObjectPositionTypePB.Before,
|
||||
objectId: widget.fieldInfo.id,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
FlowyOptionTile.text(
|
||||
showTopBorder: false,
|
||||
text: LocaleKeys.grid_field_insertRight.tr(),
|
||||
leftIcon: const FlowySvg(FlowySvgs.m_filed_insert_right_s),
|
||||
onTap: () async {
|
||||
context.pop();
|
||||
showCreateFieldBottomSheet(
|
||||
context,
|
||||
widget.viewId,
|
||||
position: OrderObjectPositionPB(
|
||||
position: OrderObjectPositionTypePB.After,
|
||||
objectId: widget.fieldInfo.id,
|
||||
return BlocProvider(
|
||||
create: (_) => FieldEditorBloc(
|
||||
viewId: widget.viewId,
|
||||
fieldController: widget.fieldController,
|
||||
field: widget.fieldInfo.field,
|
||||
)..add(const FieldEditorEvent.initial()),
|
||||
child: BlocConsumer<FieldEditorBloc, FieldEditorState>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.field.name != current.field.name,
|
||||
listener: (context, state) => controller.text = state.field.name,
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const VSpace(16),
|
||||
OptionTextField(
|
||||
controller: controller,
|
||||
type: state.field.fieldType,
|
||||
onTextChanged: (text) {
|
||||
context
|
||||
.read<FieldEditorBloc>()
|
||||
.add(FieldEditorEvent.renameField(text));
|
||||
},
|
||||
onFieldTypeChanged: (fieldType) {
|
||||
context
|
||||
.read<FieldEditorBloc>()
|
||||
.add(FieldEditorEvent.switchFieldType(fieldType));
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (!widget.fieldInfo.isPrimary) ...[
|
||||
FlowyOptionTile.text(
|
||||
showTopBorder: false,
|
||||
text: LocaleKeys.button_duplicate.tr(),
|
||||
leftIcon: const FlowySvg(FlowySvgs.m_field_copy_s),
|
||||
onTap: () async {
|
||||
context.pop();
|
||||
await service.duplicate();
|
||||
},
|
||||
),
|
||||
FlowyOptionTile.text(
|
||||
showTopBorder: false,
|
||||
text: LocaleKeys.button_delete.tr(),
|
||||
textColor: Theme.of(context).colorScheme.error,
|
||||
leftIcon: FlowySvg(
|
||||
FlowySvgs.m_field_delete_s,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
onTap: () async {
|
||||
context.pop();
|
||||
await service.delete();
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
const _Divider(),
|
||||
FlowyOptionTile.text(
|
||||
text: LocaleKeys.grid_field_editProperty.tr(),
|
||||
leftIcon: const FlowySvg(FlowySvgs.m_field_edit_s),
|
||||
onTap: () {
|
||||
showEditFieldScreen(
|
||||
context,
|
||||
widget.viewId,
|
||||
state.field,
|
||||
);
|
||||
context.pop();
|
||||
},
|
||||
),
|
||||
if (!widget.fieldInfo.isPrimary)
|
||||
FlowyOptionTile.text(
|
||||
showTopBorder: false,
|
||||
text: fieldVisibility.isVisibleState()
|
||||
? LocaleKeys.grid_field_hide.tr()
|
||||
: LocaleKeys.grid_field_show.tr(),
|
||||
leftIcon: const FlowySvg(FlowySvgs.m_field_hide_s),
|
||||
onTap: () async {
|
||||
context.pop();
|
||||
if (fieldVisibility.isVisibleState()) {
|
||||
await service.hide();
|
||||
} else {
|
||||
await service.hide();
|
||||
}
|
||||
},
|
||||
),
|
||||
if (!widget.fieldInfo.isPrimary)
|
||||
FlowyOptionTile.text(
|
||||
showTopBorder: false,
|
||||
text: LocaleKeys.grid_field_insertLeft.tr(),
|
||||
leftIcon: const FlowySvg(FlowySvgs.m_filed_insert_left_s),
|
||||
onTap: () {
|
||||
context.pop();
|
||||
mobileCreateFieldWorkflow(
|
||||
context,
|
||||
widget.viewId,
|
||||
position: OrderObjectPositionPB(
|
||||
position: OrderObjectPositionTypePB.Before,
|
||||
objectId: widget.fieldInfo.id,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
FlowyOptionTile.text(
|
||||
showTopBorder: false,
|
||||
text: LocaleKeys.grid_field_insertRight.tr(),
|
||||
leftIcon: const FlowySvg(FlowySvgs.m_filed_insert_right_s),
|
||||
onTap: () {
|
||||
context.pop();
|
||||
mobileCreateFieldWorkflow(
|
||||
context,
|
||||
widget.viewId,
|
||||
position: OrderObjectPositionPB(
|
||||
position: OrderObjectPositionTypePB.After,
|
||||
objectId: widget.fieldInfo.id,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (!widget.fieldInfo.isPrimary) ...[
|
||||
FlowyOptionTile.text(
|
||||
showTopBorder: false,
|
||||
text: LocaleKeys.button_duplicate.tr(),
|
||||
leftIcon: const FlowySvg(FlowySvgs.m_field_copy_s),
|
||||
onTap: () {
|
||||
context.pop();
|
||||
service.duplicate();
|
||||
},
|
||||
),
|
||||
FlowyOptionTile.text(
|
||||
showTopBorder: false,
|
||||
text: LocaleKeys.button_delete.tr(),
|
||||
textColor: Theme.of(context).colorScheme.error,
|
||||
leftIcon: FlowySvg(
|
||||
FlowySvgs.m_field_delete_s,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
onTap: () {
|
||||
context.pop();
|
||||
service.delete();
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+12
-3
@@ -2,6 +2,7 @@ import 'dart:ui';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/flowy_option_tile.dart';
|
||||
import 'package:appflowy/plugins/database/application/database_controller.dart';
|
||||
import 'package:appflowy/plugins/database/application/field/field_controller.dart';
|
||||
@@ -61,6 +62,7 @@ class _MobileDatabaseFieldListBody extends StatelessWidget {
|
||||
if (state.fieldContexts.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final fields = [...state.fieldContexts];
|
||||
final firstField = fields.removeAt(0);
|
||||
final firstCell = DatabaseFieldListTile(
|
||||
@@ -68,7 +70,7 @@ class _MobileDatabaseFieldListBody extends StatelessWidget {
|
||||
viewId: viewId,
|
||||
fieldController: databaseController.fieldController,
|
||||
fieldInfo: firstField,
|
||||
showTopBorder: true,
|
||||
showTopBorder: false,
|
||||
);
|
||||
final cells = fields
|
||||
.mapIndexed(
|
||||
@@ -124,9 +126,16 @@ class _MobileDatabaseFieldListBody extends StatelessWidget {
|
||||
children: [
|
||||
_divider(),
|
||||
_NewDatabaseFieldTile(viewId: viewId),
|
||||
VSpace(
|
||||
context.bottomSheetPadding(
|
||||
ignoreViewPadding: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: null,
|
||||
: VSpace(
|
||||
context.bottomSheetPadding(ignoreViewPadding: false),
|
||||
),
|
||||
itemCount: cells.length,
|
||||
itemBuilder: (context, index) => cells[index],
|
||||
);
|
||||
@@ -204,7 +213,7 @@ class _NewDatabaseFieldTile extends StatelessWidget {
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
textColor: Theme.of(context).hintColor,
|
||||
onTap: () => showCreateFieldBottomSheet(context, viewId),
|
||||
onTap: () => mobileCreateFieldWorkflow(context, viewId),
|
||||
);
|
||||
}
|
||||
}
|
||||
+553
@@ -0,0 +1,553 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/plugins/database/grid/application/sort/sort_editor_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/sort/sort_info.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
|
||||
import 'database_sort_bottom_sheet_cubit.dart';
|
||||
|
||||
class MobileSortEditor extends StatefulWidget {
|
||||
const MobileSortEditor({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<MobileSortEditor> createState() => _MobileSortEditorState();
|
||||
}
|
||||
|
||||
class _MobileSortEditorState extends State<MobileSortEditor> {
|
||||
final PageController _pageController = PageController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_pageController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => MobileSortEditorCubit(
|
||||
pageController: _pageController,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const _Header(),
|
||||
SizedBox(
|
||||
height: 400, //314,
|
||||
child: PageView.builder(
|
||||
controller: _pageController,
|
||||
itemCount: 2,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
return index == 0
|
||||
? Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
child: const _Overview(),
|
||||
)
|
||||
: const _SortDetail();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Header extends StatelessWidget {
|
||||
const _Header();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<MobileSortEditorCubit, MobileSortEditorState>(
|
||||
builder: (context, state) {
|
||||
return SizedBox(
|
||||
height: 44.0,
|
||||
child: Stack(
|
||||
children: [
|
||||
if (state.showBackButton)
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: AppBarBackButton(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 12,
|
||||
horizontal: 16,
|
||||
),
|
||||
onTap: () => context
|
||||
.read<MobileSortEditorCubit>()
|
||||
.returnToOverview(),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.grid_settings_sort.tr(),
|
||||
fontSize: 16.0,
|
||||
),
|
||||
),
|
||||
if (state.isCreatingNewSort)
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: AppBarSaveButton(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 12,
|
||||
horizontal: 16,
|
||||
),
|
||||
enable: state.newSortFieldId != null,
|
||||
onTap: () {
|
||||
_tryCreateSort(context, state);
|
||||
context.read<MobileSortEditorCubit>().returnToOverview();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _tryCreateSort(BuildContext context, MobileSortEditorState state) {
|
||||
if (state.newSortFieldId != null && state.newSortCondition != null) {
|
||||
context.read<SortEditorBloc>().add(
|
||||
SortEditorEvent.createSort(
|
||||
fieldId: state.newSortFieldId!,
|
||||
condition: state.newSortCondition!,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _Overview extends StatelessWidget {
|
||||
const _Overview();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<SortEditorBloc, SortEditorState>(
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: state.sortInfos.isEmpty
|
||||
? Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FlowySvg(
|
||||
FlowySvgs.sort_descending_s,
|
||||
size: const Size.square(60),
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
FlowyText(
|
||||
LocaleKeys.grid_sort_empty.tr(),
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: ReorderableListView.builder(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
),
|
||||
proxyDecorator: (child, index, animation) => Material(
|
||||
color: Colors.transparent,
|
||||
child: child,
|
||||
),
|
||||
onReorder: (oldIndex, newIndex) => context
|
||||
.read<SortEditorBloc>()
|
||||
.add(SortEditorEvent.reorderSort(oldIndex, newIndex)),
|
||||
itemCount: state.sortInfos.length,
|
||||
itemBuilder: (context, index) => _SortItem(
|
||||
key: ValueKey("sort_item_$index"),
|
||||
sort: state.sortInfos[index],
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: 44,
|
||||
width: double.infinity,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 14),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.fromBorderSide(
|
||||
BorderSide(
|
||||
width: 0.5,
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
),
|
||||
borderRadius: Corners.s10Border,
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
final firstField = context
|
||||
.read<SortEditorBloc>()
|
||||
.state
|
||||
.creatableFields
|
||||
.firstOrNull;
|
||||
if (firstField == null) {
|
||||
Fluttertoast.showToast(
|
||||
msg: LocaleKeys.grid_sort_cannotFindCreatableField.tr(),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
} else {
|
||||
context.read<MobileSortEditorCubit>().startCreatingSort();
|
||||
}
|
||||
},
|
||||
borderRadius: Corners.s10Border,
|
||||
child: Center(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const FlowySvg(
|
||||
FlowySvgs.add_s,
|
||||
size: Size.square(16),
|
||||
),
|
||||
const HSpace(6.0),
|
||||
FlowyText(
|
||||
LocaleKeys.grid_sort_addSort.tr(),
|
||||
fontSize: 15,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SortItem extends StatelessWidget {
|
||||
const _SortItem({super.key, required this.sort});
|
||||
|
||||
final SortInfo sort;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(
|
||||
vertical: 4.0,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).hoverColor,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => context
|
||||
.read<MobileSortEditorCubit>()
|
||||
.startEditingSort(sort.sortId),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 14,
|
||||
horizontal: 8,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Expanded(
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.grid_sort_by.tr(),
|
||||
fontSize: 15,
|
||||
),
|
||||
),
|
||||
),
|
||||
const VSpace(10),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
height: 44,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.fromBorderSide(
|
||||
BorderSide(
|
||||
width: 0.5,
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
),
|
||||
borderRadius: Corners.s10Border,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Center(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: FlowyText(
|
||||
sort.fieldInfo.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const HSpace(6.0),
|
||||
FlowySvg(
|
||||
FlowySvgs.icon_right_small_ccm_outlined_s,
|
||||
size: const Size.square(14),
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const HSpace(6),
|
||||
Expanded(
|
||||
child: Container(
|
||||
height: 44,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.fromBorderSide(
|
||||
BorderSide(
|
||||
width: 0.5,
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
),
|
||||
borderRadius: Corners.s10Border,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
),
|
||||
padding: const EdgeInsetsDirectional.only(
|
||||
start: 12,
|
||||
end: 10,
|
||||
),
|
||||
child: Center(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: FlowyText(
|
||||
sort.sortPB.condition.name,
|
||||
),
|
||||
),
|
||||
const HSpace(6.0),
|
||||
FlowySvg(
|
||||
FlowySvgs.icon_right_small_ccm_outlined_s,
|
||||
size: const Size.square(14),
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 8,
|
||||
top: 9,
|
||||
child: InkWell(
|
||||
onTap: () => context
|
||||
.read<SortEditorBloc>()
|
||||
.add(SortEditorEvent.deleteSort(sort)),
|
||||
// steal from the container LongClickReorderWidget thing
|
||||
onLongPress: () {},
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: SizedBox.square(
|
||||
dimension: 34,
|
||||
child: Center(
|
||||
child: FlowySvg(
|
||||
FlowySvgs.trash_m,
|
||||
size: const Size.square(18),
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SortDetail extends StatelessWidget {
|
||||
const _SortDetail();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isCreatingNewSort =
|
||||
context.read<MobileSortEditorCubit>().state.isCreatingNewSort;
|
||||
|
||||
return isCreatingNewSort
|
||||
? const _SortDetailContent()
|
||||
: BlocSelector<SortEditorBloc, SortEditorState, SortInfo>(
|
||||
selector: (state) => state.sortInfos.firstWhere(
|
||||
(sortInfo) =>
|
||||
sortInfo.sortId ==
|
||||
context.read<MobileSortEditorCubit>().state.editingSortId,
|
||||
),
|
||||
builder: (context, sortInfo) {
|
||||
return _SortDetailContent(sortInfo: sortInfo);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SortDetailContent extends StatelessWidget {
|
||||
const _SortDetailContent({
|
||||
this.sortInfo,
|
||||
});
|
||||
|
||||
final SortInfo? sortInfo;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const VSpace(4),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: DefaultTabController(
|
||||
length: 2,
|
||||
initialIndex: sortInfo == null
|
||||
? 0
|
||||
: sortInfo!.sortPB.condition == SortConditionPB.Ascending
|
||||
? 0
|
||||
: 1,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(3.0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: Theme.of(context).hoverColor,
|
||||
),
|
||||
child: TabBar(
|
||||
indicatorSize: TabBarIndicatorSize.label,
|
||||
labelPadding: EdgeInsets.zero,
|
||||
padding: EdgeInsets.zero,
|
||||
indicatorWeight: 0,
|
||||
indicator: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
),
|
||||
splashFactory: NoSplash.splashFactory,
|
||||
overlayColor: const MaterialStatePropertyAll(
|
||||
Colors.transparent,
|
||||
),
|
||||
onTap: (index) {
|
||||
final newCondition = index == 0
|
||||
? SortConditionPB.Ascending
|
||||
: SortConditionPB.Descending;
|
||||
_changeCondition(context, newCondition);
|
||||
},
|
||||
tabs: [
|
||||
Tab(
|
||||
height: 34,
|
||||
child: Center(
|
||||
child: FlowyText(
|
||||
LocaleKeys.grid_sort_ascending.tr(),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
height: 34,
|
||||
child: Center(
|
||||
child: FlowyText(
|
||||
LocaleKeys.grid_sort_descending.tr(),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const VSpace(20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: FlowyText(
|
||||
LocaleKeys.grid_settings_sortBy.tr().toUpperCase(),
|
||||
fontSize: 13,
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
),
|
||||
const VSpace(4.0),
|
||||
const Divider(
|
||||
height: 0.5,
|
||||
thickness: 0.5,
|
||||
),
|
||||
Expanded(
|
||||
child: BlocBuilder<SortEditorBloc, SortEditorState>(
|
||||
builder: (context, state) {
|
||||
final fields = state.allFields
|
||||
.where(
|
||||
(field) =>
|
||||
field.canCreateSort ||
|
||||
sortInfo != null && sortInfo!.fieldId == field.id,
|
||||
)
|
||||
.toList();
|
||||
return ListView.builder(
|
||||
itemCount: fields.length,
|
||||
itemBuilder: (context, index) {
|
||||
final fieldInfo = fields[index];
|
||||
final isSelected = sortInfo == null
|
||||
? context
|
||||
.watch<MobileSortEditorCubit>()
|
||||
.state
|
||||
.newSortFieldId ==
|
||||
fieldInfo.id
|
||||
: sortInfo!.fieldId == fieldInfo.id;
|
||||
return FlowyOptionTile.checkbox(
|
||||
text: fieldInfo.field.name,
|
||||
isSelected: isSelected,
|
||||
showTopBorder: false,
|
||||
onTap: () {
|
||||
if (!isSelected) {
|
||||
_changeFieldId(context, fieldInfo.id);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _changeCondition(BuildContext context, SortConditionPB newCondition) {
|
||||
if (sortInfo == null) {
|
||||
context.read<MobileSortEditorCubit>().changeSortCondition(newCondition);
|
||||
} else {
|
||||
context.read<SortEditorBloc>().add(
|
||||
SortEditorEvent.editSort(
|
||||
sortId: sortInfo!.sortId,
|
||||
condition: newCondition,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _changeFieldId(BuildContext context, String newFieldId) {
|
||||
if (sortInfo == null) {
|
||||
context.read<MobileSortEditorCubit>().changeFieldId(newFieldId);
|
||||
} else {
|
||||
context.read<SortEditorBloc>().add(
|
||||
SortEditorEvent.editSort(
|
||||
sortId: sortInfo!.sortId,
|
||||
fieldId: newFieldId,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'database_sort_bottom_sheet_cubit.freezed.dart';
|
||||
|
||||
class MobileSortEditorCubit extends Cubit<MobileSortEditorState> {
|
||||
MobileSortEditorCubit({
|
||||
required this.pageController,
|
||||
}) : super(MobileSortEditorState.initial());
|
||||
|
||||
final PageController pageController;
|
||||
|
||||
void returnToOverview() {
|
||||
_animateToPage(0);
|
||||
emit(MobileSortEditorState.initial());
|
||||
}
|
||||
|
||||
void startCreatingSort() {
|
||||
_animateToPage(1);
|
||||
emit(
|
||||
state.copyWith(
|
||||
showBackButton: true,
|
||||
isCreatingNewSort: true,
|
||||
newSortCondition: SortConditionPB.Ascending,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void startEditingSort(String sortId) {
|
||||
_animateToPage(1);
|
||||
emit(
|
||||
state.copyWith(
|
||||
showBackButton: true,
|
||||
editingSortId: sortId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// only used when creating a new sort
|
||||
void changeFieldId(String fieldId) {
|
||||
emit(state.copyWith(newSortFieldId: fieldId));
|
||||
}
|
||||
|
||||
/// only used when creating a new sort
|
||||
void changeSortCondition(SortConditionPB condition) {
|
||||
emit(state.copyWith(newSortCondition: condition));
|
||||
}
|
||||
|
||||
Future<void> _animateToPage(int page) async {
|
||||
return pageController.animateToPage(
|
||||
page,
|
||||
duration: const Duration(milliseconds: 150),
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class MobileSortEditorState with _$MobileSortEditorState {
|
||||
factory MobileSortEditorState({
|
||||
required bool showBackButton,
|
||||
required String? editingSortId,
|
||||
required bool isCreatingNewSort,
|
||||
required String? newSortFieldId,
|
||||
required SortConditionPB? newSortCondition,
|
||||
}) = _MobileSortEditorState;
|
||||
|
||||
factory MobileSortEditorState.initial() => MobileSortEditorState(
|
||||
showBackButton: false,
|
||||
editingSortId: null,
|
||||
isCreatingNewSort: false,
|
||||
newSortFieldId: null,
|
||||
newSortCondition: null,
|
||||
);
|
||||
}
|
||||
+35
-42
@@ -3,7 +3,6 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/plugins/base/drag_handler.dart';
|
||||
import 'package:appflowy/plugins/database/application/database_controller.dart';
|
||||
import 'package:appflowy/plugins/database/application/tab_bar_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
@@ -27,50 +26,44 @@ class MobileDatabaseViewList extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DraggableScrollableSheet(
|
||||
expand: false,
|
||||
snap: true,
|
||||
initialChildSize: 0.98,
|
||||
minChildSize: 0.98,
|
||||
maxChildSize: 0.98,
|
||||
builder: (context, scrollController) {
|
||||
return BlocBuilder<ViewBloc, ViewState>(
|
||||
builder: (context, state) {
|
||||
final views = [state.view, ...state.view.childViews];
|
||||
return BlocBuilder<ViewBloc, ViewState>(
|
||||
builder: (context, state) {
|
||||
final views = [state.view, ...state.view.childViews];
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
const DragHandler(),
|
||||
_Header(
|
||||
title: LocaleKeys.grid_settings_viewList.plural(
|
||||
context.watch<DatabaseTabBarBloc>().state.tabBars.length,
|
||||
namedArgs: {
|
||||
'count':
|
||||
'${context.watch<DatabaseTabBarBloc>().state.tabBars.length}',
|
||||
},
|
||||
return Column(
|
||||
children: [
|
||||
_Header(
|
||||
title: LocaleKeys.grid_settings_viewList.plural(
|
||||
context.watch<DatabaseTabBarBloc>().state.tabBars.length,
|
||||
namedArgs: {
|
||||
'count':
|
||||
'${context.watch<DatabaseTabBarBloc>().state.tabBars.length}',
|
||||
},
|
||||
),
|
||||
showBackButton: false,
|
||||
useFilledDoneButton: false,
|
||||
onDone: (context) => Navigator.pop(context),
|
||||
),
|
||||
Expanded(
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.zero,
|
||||
children: [
|
||||
...views.mapIndexed(
|
||||
(index, view) => MobileDatabaseViewListButton(
|
||||
view: view,
|
||||
showTopBorder: index == 0,
|
||||
),
|
||||
),
|
||||
showBackButton: false,
|
||||
useFilledDoneButton: false,
|
||||
onDone: (context) => Navigator.pop(context),
|
||||
),
|
||||
SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
child: Column(
|
||||
children: [
|
||||
...views.mapIndexed(
|
||||
(index, view) => MobileDatabaseViewListButton(
|
||||
view: view,
|
||||
showTopBorder: index == 0,
|
||||
),
|
||||
),
|
||||
const VSpace(20),
|
||||
const MobileNewDatabaseViewButton(),
|
||||
],
|
||||
const VSpace(20),
|
||||
const MobileNewDatabaseViewButton(),
|
||||
VSpace(
|
||||
context.bottomSheetPadding(ignoreViewPadding: false),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
+27
-19
@@ -1,6 +1,6 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/show_transition_bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart';
|
||||
import 'package:appflowy/plugins/database/application/database_controller.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
@@ -30,18 +30,13 @@ class MobileDatabaseViewQuickActions extends StatelessWidget {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_actionButton(context, _Action.edit, () {
|
||||
_actionButton(context, _Action.edit, () async {
|
||||
final bloc = context.read<ViewBloc>();
|
||||
context.pop();
|
||||
showMobileBottomSheet(
|
||||
await showTransitionMobileBottomSheet(
|
||||
context,
|
||||
showHeader: true,
|
||||
showDoneButton: true,
|
||||
title: LocaleKeys.grid_settings_editView.tr(),
|
||||
enableDraggableScrollable: true,
|
||||
initialChildSize: 0.98,
|
||||
minChildSize: 0.98,
|
||||
maxChildSize: 0.98,
|
||||
builder: (_) => BlocProvider.value(
|
||||
value: bloc,
|
||||
child: MobileEditDatabaseViewScreen(
|
||||
@@ -49,20 +44,31 @@ class MobileDatabaseViewQuickActions extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
);
|
||||
if (context.mounted) {
|
||||
context.pop();
|
||||
}
|
||||
}),
|
||||
if (!isInline) ...[
|
||||
_divider(),
|
||||
_actionButton(context, _Action.duplicate, () {
|
||||
_divider(),
|
||||
_actionButton(
|
||||
context,
|
||||
_Action.duplicate,
|
||||
() {
|
||||
context.read<ViewBloc>().add(const ViewEvent.duplicate());
|
||||
context.pop();
|
||||
}),
|
||||
_divider(),
|
||||
_actionButton(context, _Action.delete, () {
|
||||
},
|
||||
!isInline,
|
||||
),
|
||||
_divider(),
|
||||
_actionButton(
|
||||
context,
|
||||
_Action.delete,
|
||||
() {
|
||||
context.read<ViewBloc>().add(const ViewEvent.delete());
|
||||
context.pop();
|
||||
}),
|
||||
_divider(),
|
||||
],
|
||||
},
|
||||
!isInline,
|
||||
),
|
||||
_divider(),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -70,14 +76,16 @@ class MobileDatabaseViewQuickActions extends StatelessWidget {
|
||||
Widget _actionButton(
|
||||
BuildContext context,
|
||||
_Action action,
|
||||
VoidCallback onTap,
|
||||
) {
|
||||
VoidCallback onTap, [
|
||||
bool enable = true,
|
||||
]) {
|
||||
return MobileQuickActionButton(
|
||||
icon: action.icon,
|
||||
text: action.label,
|
||||
textColor: action.color(context),
|
||||
iconColor: action.color(context),
|
||||
onTap: onTap,
|
||||
enable: enable,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+13
-20
@@ -3,8 +3,8 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/flowy_option_tile.dart';
|
||||
import 'package:appflowy/plugins/database/application/database_controller.dart';
|
||||
import 'package:appflowy/plugins/database/application/database_view_service.dart';
|
||||
import 'package:appflowy/plugins/database/application/layout/layout_service.dart';
|
||||
import 'package:appflowy/plugins/database/domain/database_view_service.dart';
|
||||
import 'package:appflowy/plugins/database/domain/layout_service.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/database_layout_ext.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
@@ -33,8 +33,7 @@ class MobileEditDatabaseViewScreen extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<ViewBloc, ViewState>(
|
||||
builder: (context, state) {
|
||||
return ListView(
|
||||
shrinkWrap: true,
|
||||
return Column(
|
||||
children: [
|
||||
_NameAndIcon(view: state.view),
|
||||
_divider(),
|
||||
@@ -91,13 +90,15 @@ class _NameAndIconState extends State<_NameAndIcon> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FlowyOptionTile.textField(
|
||||
autofocus: true,
|
||||
showTopBorder: false,
|
||||
controller: textEditingController,
|
||||
onTextChanged: (text) {
|
||||
context.read<ViewBloc>().add(ViewEvent.rename(text));
|
||||
},
|
||||
return Material(
|
||||
child: FlowyOptionTile.textField(
|
||||
autofocus: true,
|
||||
showTopBorder: false,
|
||||
controller: textEditingController,
|
||||
onTextChanged: (text) {
|
||||
context.read<ViewBloc>().add(ViewEvent.rename(text));
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -228,19 +229,12 @@ class DatabaseViewSettingTile extends StatelessWidget {
|
||||
}
|
||||
|
||||
if (setting == DatabaseViewSettings.fields) {
|
||||
await showMobileBottomSheet(
|
||||
await showTransitionMobileBottomSheet(
|
||||
context,
|
||||
useSafeArea: false,
|
||||
resizeToAvoidBottomInset: false,
|
||||
showDragHandle: true,
|
||||
showHeader: true,
|
||||
showBackButton: true,
|
||||
title: LocaleKeys.grid_settings_properties.tr(),
|
||||
showDivider: true,
|
||||
enableDraggableScrollable: true,
|
||||
initialChildSize: 0.98,
|
||||
minChildSize: 0.98,
|
||||
maxChildSize: 0.98,
|
||||
builder: (_) {
|
||||
return BlocProvider.value(
|
||||
value: context.read<ViewBloc>(),
|
||||
@@ -257,7 +251,6 @@ class DatabaseViewSettingTile extends StatelessWidget {
|
||||
if (setting == DatabaseViewSettings.board) {
|
||||
await showMobileBottomSheet<DatabaseLayoutPB>(
|
||||
context,
|
||||
resizeToAvoidBottomInset: false,
|
||||
builder: (context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 24, bottom: 46),
|
||||
|
||||
+6
-4
@@ -35,10 +35,12 @@ class MobileFavoriteScreen extends StatelessWidget {
|
||||
},
|
||||
(error) => null,
|
||||
);
|
||||
final userProfile =
|
||||
snapshots.data?[1].fold((error) => null, (userProfilePB) {
|
||||
return userProfilePB as UserProfilePB?;
|
||||
});
|
||||
final userProfile = snapshots.data?[1].fold(
|
||||
(userProfilePB) {
|
||||
return userProfilePB as UserProfilePB?;
|
||||
},
|
||||
(error) => null,
|
||||
);
|
||||
|
||||
// In the unlikely case either of the above is null, eg.
|
||||
// when a workspace is already open this can happen.
|
||||
|
||||
@@ -41,10 +41,12 @@ class MobileHomeScreen extends StatelessWidget {
|
||||
},
|
||||
(error) => null,
|
||||
);
|
||||
final userProfile =
|
||||
snapshots.data?[1].fold((error) => null, (userProfilePB) {
|
||||
return userProfilePB as UserProfilePB?;
|
||||
});
|
||||
final userProfile = snapshots.data?[1].fold(
|
||||
(userProfilePB) {
|
||||
return userProfilePB as UserProfilePB?;
|
||||
},
|
||||
(error) => null,
|
||||
);
|
||||
|
||||
// In the unlikely case either of the above is null, eg.
|
||||
// when a workspace is already open this can happen.
|
||||
|
||||
+9
-6
@@ -35,12 +35,15 @@ class _MobileHomeSettingPageState extends State<MobileHomeSettingPage> {
|
||||
return const Center(child: CircularProgressIndicator.adaptive());
|
||||
}
|
||||
|
||||
final userProfile = snapshot.data?.fold((error) {
|
||||
errorMsg = error.msg;
|
||||
return null;
|
||||
}, (userProfile) {
|
||||
return userProfile;
|
||||
});
|
||||
final userProfile = snapshot.data?.fold(
|
||||
(userProfile) {
|
||||
return userProfile;
|
||||
},
|
||||
(error) {
|
||||
errorMsg = error.msg;
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
appBar: FlowyAppBar(
|
||||
|
||||
+5
-4
@@ -1,9 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/core/helpers/url_launcher.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/startup/tasks/device_info_task.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../widgets/widgets.dart';
|
||||
|
||||
@@ -22,14 +23,14 @@ class AboutSettingGroup extends StatelessWidget {
|
||||
trailing: const Icon(
|
||||
Icons.chevron_right,
|
||||
),
|
||||
onTap: () => safeLaunchUrl('https://appflowy.io/privacy/mobile'),
|
||||
onTap: () => afLaunchUrlString('https://appflowy.io/privacy/app'),
|
||||
),
|
||||
MobileSettingItem(
|
||||
name: LocaleKeys.settings_mobile_termsAndConditions.tr(),
|
||||
trailing: const Icon(
|
||||
Icons.chevron_right,
|
||||
),
|
||||
onTap: () => safeLaunchUrl('https://appflowy.io/terms'),
|
||||
onTap: () => afLaunchUrlString('https://appflowy.io/terms/app'),
|
||||
),
|
||||
MobileSettingItem(
|
||||
name: LocaleKeys.settings_mobile_version.tr(),
|
||||
|
||||
+2
@@ -1,5 +1,6 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/setting/appearance/rtl_setting.dart';
|
||||
import 'package:appflowy/mobile/presentation/setting/appearance/text_scale_setting.dart';
|
||||
import 'package:appflowy/mobile/presentation/setting/appearance/theme_setting.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -18,6 +19,7 @@ class AppearanceSettingGroup extends StatelessWidget {
|
||||
settingItemList: const [
|
||||
ThemeSetting(),
|
||||
FontSetting(),
|
||||
TextScaleSetting(),
|
||||
RTLSetting(),
|
||||
],
|
||||
);
|
||||
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/more_view_actions/widgets/font_size_stepper.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../setting.dart';
|
||||
|
||||
const int _divisions = 4;
|
||||
|
||||
class TextScaleSetting extends StatelessWidget {
|
||||
const TextScaleSetting({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final textScaleFactor =
|
||||
context.watch<AppearanceSettingsCubit>().state.textScaleFactor;
|
||||
return MobileSettingItem(
|
||||
name: LocaleKeys.settings_appearance_fontScaleFactor.tr(),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FlowyText(
|
||||
// map the text scale factor to the 0-1
|
||||
// 0.8 - 0.0
|
||||
// 0.9 - 0.5
|
||||
// 1.0 - 1.0
|
||||
((_divisions + 1) * textScaleFactor - _divisions)
|
||||
.toStringAsFixed(2),
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
const Icon(Icons.chevron_right),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
showHeader: true,
|
||||
showDragHandle: true,
|
||||
showDivider: false,
|
||||
showCloseButton: false,
|
||||
title: LocaleKeys.settings_appearance_fontScaleFactor.tr(),
|
||||
builder: (context) {
|
||||
return FontSizeStepper(
|
||||
value: textScaleFactor,
|
||||
minimumValue: 0.8,
|
||||
maximumValue: 1.0,
|
||||
divisions: _divisions,
|
||||
onChanged: (newTextScaleFactor) {
|
||||
context
|
||||
.read<AppearanceSettingsCubit>()
|
||||
.setTextScaleFactor(newTextScaleFactor);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/setting/font/font_picker_screen.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ class _LanguageSettingGroupState extends State<LanguageSettingGroup> {
|
||||
final newLocale =
|
||||
await context.push<Locale>(LanguagePickerScreen.routeName);
|
||||
if (newLocale != null && newLocale != locale) {
|
||||
if (mounted) {
|
||||
if (context.mounted) {
|
||||
context
|
||||
.read<AppearanceSettingsCubit>()
|
||||
.setLocale(context, newLocale);
|
||||
|
||||
+4
-2
@@ -1,3 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
@@ -5,10 +7,10 @@ import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/application/user/prelude.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../widgets/widgets.dart';
|
||||
|
||||
import 'personal_info.dart';
|
||||
|
||||
class PersonalInfoSettingGroup extends StatelessWidget {
|
||||
@@ -34,7 +36,7 @@ class PersonalInfoSettingGroup extends StatelessWidget {
|
||||
settingItemList: [
|
||||
MobileSettingItem(
|
||||
name: userName,
|
||||
subtitle: isAuthEnabled
|
||||
subtitle: isAuthEnabled && userProfile.email.isNotEmpty
|
||||
? Text(
|
||||
userProfile.email,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
|
||||
+31
-3
@@ -1,11 +1,14 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/core/helpers/url_launcher.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/shared/appflowy_cache_manager.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/util/share_log_files.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
@@ -28,7 +31,7 @@ class SupportSettingGroup extends StatelessWidget {
|
||||
trailing: const Icon(
|
||||
Icons.chevron_right,
|
||||
),
|
||||
onTap: () => safeLaunchUrl('https://discord.gg/JucBXeU2FE'),
|
||||
onTap: () => afLaunchUrlString('https://discord.gg/JucBXeU2FE'),
|
||||
),
|
||||
MobileSettingItem(
|
||||
name: LocaleKeys.workspace_errorActions_reportIssue.tr(),
|
||||
@@ -50,6 +53,31 @@ class SupportSettingGroup extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
MobileSettingItem(
|
||||
name: LocaleKeys.settings_files_clearCache.tr(),
|
||||
trailing: const Icon(
|
||||
Icons.chevron_right,
|
||||
),
|
||||
onTap: () async {
|
||||
await showFlowyMobileConfirmDialog(
|
||||
context,
|
||||
title: FlowyText(
|
||||
LocaleKeys.settings_files_areYouSureToClearCache.tr(),
|
||||
maxLines: 2,
|
||||
),
|
||||
content: FlowyText(
|
||||
LocaleKeys.settings_files_clearCacheDesc.tr(),
|
||||
fontSize: 12,
|
||||
maxLines: 4,
|
||||
),
|
||||
actionButtonTitle: LocaleKeys.button_yes.tr(),
|
||||
actionButtonColor: Theme.of(context).colorScheme.error,
|
||||
onActionButtonPressed: () async {
|
||||
await getIt<FlowyCacheManager>().clearAllCache();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -73,7 +101,7 @@ class _ReportIssuesWidget extends StatelessWidget {
|
||||
text: LocaleKeys.workspace_errorActions_reportIssueOnGithub.tr(),
|
||||
onTap: () {
|
||||
final String os = Platform.operatingSystem;
|
||||
safeLaunchUrl(
|
||||
afLaunchUrlString(
|
||||
'https://github.com/AppFlowy-IO/AppFlowy/issues/new?assignees=&labels=&projects=&template=bug_report.yaml&title=[Bug]%20Mobile:%20&version=$version&os=$os',
|
||||
);
|
||||
},
|
||||
|
||||
+4
-6
@@ -5,6 +5,7 @@ import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/application/sign_in_bloc.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/sign_in_or_logout_button.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -27,12 +28,9 @@ class UserSessionSettingGroup extends StatelessWidget {
|
||||
create: (context) => getIt<SignInBloc>(),
|
||||
child: BlocConsumer<SignInBloc, SignInState>(
|
||||
listener: (context, state) {
|
||||
state.successOrFail.fold(
|
||||
() => null,
|
||||
(result) => result.fold(
|
||||
(l) {},
|
||||
(r) async => runAppFlowy(),
|
||||
),
|
||||
state.successOrFail?.fold(
|
||||
(result) => runAppFlowy(),
|
||||
(e) => Log.error(e),
|
||||
);
|
||||
},
|
||||
builder: (context, state) {
|
||||
|
||||
+11
-3
@@ -10,6 +10,7 @@ class MobileQuickActionButton extends StatelessWidget {
|
||||
required this.text,
|
||||
this.textColor,
|
||||
this.iconColor,
|
||||
this.enable = true,
|
||||
});
|
||||
|
||||
final VoidCallback onTap;
|
||||
@@ -17,14 +18,21 @@ class MobileQuickActionButton extends StatelessWidget {
|
||||
final String text;
|
||||
final Color? textColor;
|
||||
final Color? iconColor;
|
||||
final bool enable;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
onTap: () {
|
||||
if (enable) {
|
||||
onTap();
|
||||
}
|
||||
},
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
overlayColor:
|
||||
enable ? null : const MaterialStatePropertyAll(Colors.transparent),
|
||||
splashColor: Colors.transparent,
|
||||
child: Container(
|
||||
height: 44,
|
||||
@@ -34,14 +42,14 @@ class MobileQuickActionButton extends StatelessWidget {
|
||||
FlowySvg(
|
||||
icon,
|
||||
size: const Size.square(20),
|
||||
color: iconColor,
|
||||
color: enable ? iconColor : Theme.of(context).disabledColor,
|
||||
),
|
||||
const HSpace(12),
|
||||
Expanded(
|
||||
child: FlowyText(
|
||||
text,
|
||||
fontSize: 15,
|
||||
color: textColor,
|
||||
color: enable ? textColor : Theme.of(context).disabledColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
+6
-5
@@ -1,9 +1,10 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/core/helpers/url_launcher.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
enum _FlowyMobileStateContainerType {
|
||||
@@ -80,7 +81,7 @@ class FlowyMobileStateContainer extends StatelessWidget {
|
||||
onPressed: () {
|
||||
final String? version = snapshot.data?.version;
|
||||
final String os = Platform.operatingSystem;
|
||||
safeLaunchUrl(
|
||||
afLaunchUrlString(
|
||||
'https://github.com/AppFlowy-IO/AppFlowy/issues/new?assignees=&labels=&projects=&template=bug_report.yaml&title=[Bug]%20Mobile:%20&version=$version&os=$os&context=Error%20log:%20$errorMsg',
|
||||
);
|
||||
},
|
||||
@@ -90,7 +91,7 @@ class FlowyMobileStateContainer extends StatelessWidget {
|
||||
),
|
||||
OutlinedButton(
|
||||
onPressed: () =>
|
||||
safeLaunchUrl('https://discord.gg/JucBXeU2FE'),
|
||||
afLaunchUrlString('https://discord.gg/JucBXeU2FE'),
|
||||
child: Text(
|
||||
LocaleKeys.workspace_errorActions_reachOut.tr(),
|
||||
),
|
||||
|
||||
@@ -218,7 +218,7 @@ class FlowyOptionTile extends StatelessWidget {
|
||||
|
||||
final padding = EdgeInsets.symmetric(
|
||||
horizontal: leading == null ? 0.0 : 12.0,
|
||||
vertical: 16.0,
|
||||
vertical: 14.0,
|
||||
);
|
||||
|
||||
return Expanded(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DragHandler extends StatelessWidget {
|
||||
const DragHandler({
|
||||
class DragHandle extends StatelessWidget {
|
||||
const DragHandle({
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
||||
+5
-5
@@ -4,11 +4,11 @@ import 'dart:typed_data';
|
||||
import 'package:appflowy/core/notification/grid_notification.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:flowy_infra/notifier.dart';
|
||||
|
||||
typedef UpdateCalculationValue
|
||||
= Either<CalculationChangesetNotificationPB, FlowyError>;
|
||||
= FlowyResult<CalculationChangesetNotificationPB, FlowyError>;
|
||||
|
||||
class CalculationsListener {
|
||||
CalculationsListener({required this.viewId});
|
||||
@@ -31,15 +31,15 @@ class CalculationsListener {
|
||||
|
||||
void _handler(
|
||||
DatabaseNotification ty,
|
||||
Either<Uint8List, FlowyError> result,
|
||||
FlowyResult<Uint8List, FlowyError> result,
|
||||
) {
|
||||
switch (ty) {
|
||||
case DatabaseNotification.DidUpdateCalculation:
|
||||
_calculationNotifier?.value = result.fold(
|
||||
(payload) => left(
|
||||
(payload) => FlowyResult.success(
|
||||
CalculationChangesetNotificationPB.fromBuffer(payload),
|
||||
),
|
||||
(err) => right(err),
|
||||
(err) => FlowyResult.failure(err),
|
||||
);
|
||||
default:
|
||||
break;
|
||||
|
||||
+4
-2
@@ -1,7 +1,7 @@
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
|
||||
class CalculationsBackendService {
|
||||
const CalculationsBackendService({required this.viewId});
|
||||
@@ -9,7 +9,9 @@ class CalculationsBackendService {
|
||||
final String viewId;
|
||||
|
||||
// Get Calculations (initial fetch)
|
||||
Future<Either<RepeatedCalculationsPB, FlowyError>> getCalculations() async {
|
||||
|
||||
Future<FlowyResult<RepeatedCalculationsPB, FlowyError>>
|
||||
getCalculations() async {
|
||||
final payload = DatabaseViewIdPB()..value = viewId;
|
||||
|
||||
return DatabaseEventGetAllCalculations(payload).send();
|
||||
|
||||
+3
-3
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
@@ -86,7 +87,6 @@ class CheckboxCellState with _$CheckboxCellState {
|
||||
}
|
||||
}
|
||||
|
||||
bool _isSelected(String? cellData) {
|
||||
// The backend use "Yes" and "No" to represent the checkbox cell data.
|
||||
return cellData == "Yes";
|
||||
bool _isSelected(CheckboxCellDataPB? cellData) {
|
||||
return cellData != null && cellData.isChecked;
|
||||
}
|
||||
+21
-31
@@ -1,7 +1,7 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/checklist_cell_service.dart';
|
||||
import 'package:appflowy/plugins/database/domain/checklist_cell_service.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/checklist_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/select_option_entities.pb.dart';
|
||||
@@ -11,7 +11,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
part 'checklist_cell_bloc.freezed.dart';
|
||||
|
||||
class ChecklistSelectOption {
|
||||
ChecklistSelectOption(this.isSelected, this.data);
|
||||
ChecklistSelectOption({required this.isSelected, required this.data});
|
||||
|
||||
final bool isSelected;
|
||||
final SelectOptionPB data;
|
||||
@@ -26,6 +26,7 @@ class ChecklistCellBloc extends Bloc<ChecklistCellEvent, ChecklistCellState> {
|
||||
),
|
||||
super(ChecklistCellState.initial(cellController)) {
|
||||
_dispatch();
|
||||
_startListening();
|
||||
}
|
||||
|
||||
final ChecklistCellController cellController;
|
||||
@@ -46,9 +47,6 @@ class ChecklistCellBloc extends Bloc<ChecklistCellEvent, ChecklistCellState> {
|
||||
on<ChecklistCellEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
initial: () {
|
||||
_startListening();
|
||||
},
|
||||
didReceiveOptions: (data) {
|
||||
if (data == null) {
|
||||
emit(
|
||||
@@ -71,8 +69,8 @@ class ChecklistCellBloc extends Bloc<ChecklistCellEvent, ChecklistCellState> {
|
||||
updateTaskName: (option, name) {
|
||||
_updateOption(option, name);
|
||||
},
|
||||
selectTask: (option) async {
|
||||
await _checklistCellService.select(optionId: option.id);
|
||||
selectTask: (id) async {
|
||||
await _checklistCellService.select(optionId: id);
|
||||
},
|
||||
createNewTask: (name) async {
|
||||
final result = await _checklistCellService.create(name: name);
|
||||
@@ -81,8 +79,8 @@ class ChecklistCellBloc extends Bloc<ChecklistCellEvent, ChecklistCellState> {
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
},
|
||||
deleteTask: (option) async {
|
||||
await _deleteOption([option]);
|
||||
deleteTask: (id) async {
|
||||
await _deleteOption([id]);
|
||||
},
|
||||
);
|
||||
},
|
||||
@@ -102,21 +100,17 @@ class ChecklistCellBloc extends Bloc<ChecklistCellEvent, ChecklistCellState> {
|
||||
void _updateOption(SelectOptionPB option, String name) async {
|
||||
final result =
|
||||
await _checklistCellService.updateName(option: option, name: name);
|
||||
|
||||
result.fold((l) => null, (err) => Log.error(err));
|
||||
}
|
||||
|
||||
Future<void> _deleteOption(List<SelectOptionPB> options) async {
|
||||
final result = await _checklistCellService.delete(
|
||||
optionIds: options.map((e) => e.id).toList(),
|
||||
);
|
||||
Future<void> _deleteOption(List<String> options) async {
|
||||
final result = await _checklistCellService.delete(optionIds: options);
|
||||
result.fold((l) => null, (err) => Log.error(err));
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ChecklistCellEvent with _$ChecklistCellEvent {
|
||||
const factory ChecklistCellEvent.initial() = _InitialCell;
|
||||
const factory ChecklistCellEvent.didReceiveOptions(
|
||||
ChecklistCellDataPB? data,
|
||||
) = _DidReceiveCellUpdate;
|
||||
@@ -124,12 +118,10 @@ class ChecklistCellEvent with _$ChecklistCellEvent {
|
||||
SelectOptionPB option,
|
||||
String name,
|
||||
) = _UpdateTaskName;
|
||||
const factory ChecklistCellEvent.selectTask(SelectOptionPB task) =
|
||||
_SelectTask;
|
||||
const factory ChecklistCellEvent.selectTask(String taskId) = _SelectTask;
|
||||
const factory ChecklistCellEvent.createNewTask(String description) =
|
||||
_CreateNewTask;
|
||||
const factory ChecklistCellEvent.deleteTask(SelectOptionPB option) =
|
||||
_DeleteTask;
|
||||
const factory ChecklistCellEvent.deleteTask(String taskId) = _DeleteTask;
|
||||
}
|
||||
|
||||
@freezed
|
||||
@@ -157,16 +149,14 @@ List<ChecklistSelectOption> _makeChecklistSelectOptions(
|
||||
if (data == null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
final List<ChecklistSelectOption> options = [];
|
||||
final List<SelectOptionPB> allOptions = List.from(data.options);
|
||||
final selectedOptionIds = data.selectedOptions.map((e) => e.id).toList();
|
||||
|
||||
for (final option in allOptions) {
|
||||
options.add(
|
||||
ChecklistSelectOption(selectedOptionIds.contains(option.id), option),
|
||||
);
|
||||
}
|
||||
|
||||
return options;
|
||||
return data.options
|
||||
.map(
|
||||
(option) => ChecklistSelectOption(
|
||||
isSelected: data.selectedOptions.any(
|
||||
(selected) => selected.id == option.id,
|
||||
),
|
||||
data: option,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
+2
-2
@@ -2,8 +2,8 @@ import 'dart:async';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/date_cell_service.dart';
|
||||
import 'package:appflowy/plugins/database/application/field/field_service.dart';
|
||||
import 'package:appflowy/plugins/database/domain/date_cell_service.dart';
|
||||
import 'package:appflowy/plugins/database/domain/field_service.dart';
|
||||
import 'package:appflowy/plugins/database/application/field/type_option/type_option_data_parser.dart';
|
||||
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
|
||||
import 'package:appflowy/user/application/reminder/reminder_extension.dart';
|
||||
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database/application/field/type_option/type_option_data_parser.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'relation_cell_bloc.freezed.dart';
|
||||
|
||||
class RelationCellBloc extends Bloc<RelationCellEvent, RelationCellState> {
|
||||
RelationCellBloc({required this.cellController})
|
||||
: super(RelationCellState.initial()) {
|
||||
_dispatch();
|
||||
_startListening();
|
||||
_init();
|
||||
}
|
||||
|
||||
final RelationCellController cellController;
|
||||
void Function()? _onCellChangedFn;
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _dispatch() {
|
||||
on<RelationCellEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
didUpdateCell: (RelationCellDataPB? cellData) async {
|
||||
if (cellData == null || cellData.rowIds.isEmpty) {
|
||||
emit(state.copyWith(rows: const []));
|
||||
return;
|
||||
}
|
||||
final payload = RepeatedRowIdPB(
|
||||
databaseId: state.relatedDatabaseId,
|
||||
rowIds: cellData.rowIds,
|
||||
);
|
||||
final result =
|
||||
await DatabaseEventGetRelatedRowDatas(payload).send();
|
||||
final rows = result.fold(
|
||||
(data) => data.rows,
|
||||
(err) {
|
||||
Log.error(err);
|
||||
return const <RelatedRowDataPB>[];
|
||||
},
|
||||
);
|
||||
emit(state.copyWith(rows: rows));
|
||||
},
|
||||
didUpdateRelationDatabaseId: (databaseId) {
|
||||
emit(state.copyWith(relatedDatabaseId: databaseId));
|
||||
},
|
||||
selectRow: (rowId) async {
|
||||
await _handleSelectRow(rowId);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellController.addListener(
|
||||
onCellChanged: (data) {
|
||||
if (!isClosed) {
|
||||
add(RelationCellEvent.didUpdateCell(data));
|
||||
}
|
||||
},
|
||||
onCellFieldChanged: (field) {
|
||||
if (!isClosed) {
|
||||
// hack: SingleFieldListener receives notification before
|
||||
// FieldController's copy is updated.
|
||||
Future.delayed(const Duration(milliseconds: 50), () {
|
||||
final RelationTypeOptionPB typeOption =
|
||||
cellController.getTypeOption(RelationTypeOptionDataParser());
|
||||
add(
|
||||
RelationCellEvent.didUpdateRelationDatabaseId(
|
||||
typeOption.databaseId,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _init() {
|
||||
final RelationTypeOptionPB typeOption =
|
||||
cellController.getTypeOption(RelationTypeOptionDataParser());
|
||||
add(RelationCellEvent.didUpdateRelationDatabaseId(typeOption.databaseId));
|
||||
final cellData = cellController.getCellData();
|
||||
add(RelationCellEvent.didUpdateCell(cellData));
|
||||
}
|
||||
|
||||
Future<void> _handleSelectRow(String rowId) async {
|
||||
final payload = RelationCellChangesetPB(
|
||||
viewId: cellController.viewId,
|
||||
cellId: CellIdPB(
|
||||
viewId: cellController.viewId,
|
||||
fieldId: cellController.fieldId,
|
||||
rowId: cellController.rowId,
|
||||
),
|
||||
);
|
||||
if (state.rows.any((row) => row.rowId == rowId)) {
|
||||
payload.removedRowIds.add(rowId);
|
||||
} else {
|
||||
payload.insertedRowIds.add(rowId);
|
||||
}
|
||||
final result = await DatabaseEventUpdateRelationCell(payload).send();
|
||||
result.fold((l) => null, (err) => Log.error(err));
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class RelationCellEvent with _$RelationCellEvent {
|
||||
const factory RelationCellEvent.didUpdateRelationDatabaseId(
|
||||
String databaseId,
|
||||
) = _DidUpdateRelationDatabaseId;
|
||||
const factory RelationCellEvent.didUpdateCell(RelationCellDataPB? data) =
|
||||
_DidUpdateCell;
|
||||
const factory RelationCellEvent.selectRow(String rowId) = _SelectRowId;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class RelationCellState with _$RelationCellState {
|
||||
const factory RelationCellState({
|
||||
required String relatedDatabaseId,
|
||||
required List<RelatedRowDataPB> rows,
|
||||
}) = _RelationCellState;
|
||||
|
||||
factory RelationCellState.initial() =>
|
||||
const RelationCellState(relatedDatabaseId: "", rows: []);
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'relation_row_search_bloc.freezed.dart';
|
||||
|
||||
class RelationRowSearchBloc
|
||||
extends Bloc<RelationRowSearchEvent, RelationRowSearchState> {
|
||||
RelationRowSearchBloc({
|
||||
required this.databaseId,
|
||||
}) : super(RelationRowSearchState.initial()) {
|
||||
_dispatch();
|
||||
_init();
|
||||
}
|
||||
|
||||
final String databaseId;
|
||||
final List<RelatedRowDataPB> allRows = [];
|
||||
|
||||
void _dispatch() {
|
||||
on<RelationRowSearchEvent>(
|
||||
(event, emit) {
|
||||
event.when(
|
||||
didUpdateRowList: (List<RelatedRowDataPB> rowList) {
|
||||
allRows.clear();
|
||||
allRows.addAll(rowList);
|
||||
emit(state.copyWith(filteredRows: allRows));
|
||||
},
|
||||
updateFilter: (String filter) => _updateFilter(filter, emit),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _init() async {
|
||||
final payload = DatabaseIdPB(value: databaseId);
|
||||
final result = await DatabaseEventGetRelatedDatabaseRows(payload).send();
|
||||
result.fold(
|
||||
(data) => add(RelationRowSearchEvent.didUpdateRowList(data.rows)),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
}
|
||||
|
||||
void _updateFilter(String filter, Emitter<RelationRowSearchState> emit) {
|
||||
final rows = [...allRows];
|
||||
if (filter.isNotEmpty) {
|
||||
rows.retainWhere(
|
||||
(row) => row.name.toLowerCase().contains(filter.toLowerCase()),
|
||||
);
|
||||
}
|
||||
emit(state.copyWith(filter: filter, filteredRows: rows));
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class RelationRowSearchEvent with _$RelationRowSearchEvent {
|
||||
const factory RelationRowSearchEvent.didUpdateRowList(
|
||||
List<RelatedRowDataPB> rowList,
|
||||
) = _DidUpdateRowList;
|
||||
const factory RelationRowSearchEvent.updateFilter(String filter) =
|
||||
_UpdateFilter;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class RelationRowSearchState with _$RelationRowSearchState {
|
||||
const factory RelationRowSearchState({
|
||||
required String filter,
|
||||
required List<RelatedRowDataPB> filteredRows,
|
||||
}) = _RelationRowSearchState;
|
||||
|
||||
factory RelationRowSearchState.initial() => const RelationRowSearchState(
|
||||
filter: "",
|
||||
filteredRows: [],
|
||||
);
|
||||
}
|
||||
+26
-30
@@ -1,12 +1,10 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/select_option_cell_service.dart';
|
||||
import 'package:appflowy/plugins/database/domain/select_option_cell_service.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/select_option_entities.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
@@ -52,7 +50,7 @@ class SelectOptionCellEditorBloc
|
||||
await _createOption(optionName);
|
||||
emit(
|
||||
state.copyWith(
|
||||
filter: none(),
|
||||
filter: null,
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -164,7 +162,7 @@ class SelectOptionCellEditorBloc
|
||||
}
|
||||
|
||||
// clear the filter
|
||||
emit(state.copyWith(filter: none()));
|
||||
emit(state.copyWith(filter: null));
|
||||
}
|
||||
|
||||
void _selectMultipleOptions(List<String> optionNames) {
|
||||
@@ -186,12 +184,12 @@ class SelectOptionCellEditorBloc
|
||||
|
||||
void _filterOption(String optionName, Emitter<SelectOptionEditorState> emit) {
|
||||
final _MakeOptionResult result = _makeOptions(
|
||||
Some(optionName),
|
||||
optionName,
|
||||
state.allOptions,
|
||||
);
|
||||
emit(
|
||||
state.copyWith(
|
||||
filter: Some(optionName),
|
||||
filter: optionName,
|
||||
options: result.options,
|
||||
createOption: result.createOption,
|
||||
),
|
||||
@@ -201,7 +199,7 @@ class SelectOptionCellEditorBloc
|
||||
Future<void> _loadOptions() async {
|
||||
final result = await _selectOptionService.getCellData();
|
||||
if (isClosed) {
|
||||
Log.warn("Unexpected closing the bloc");
|
||||
Log.warn("Unexpecteded closing the bloc");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -220,28 +218,26 @@ class SelectOptionCellEditorBloc
|
||||
}
|
||||
|
||||
_MakeOptionResult _makeOptions(
|
||||
Option<String> filter,
|
||||
String? filter,
|
||||
List<SelectOptionPB> allOptions,
|
||||
) {
|
||||
final List<SelectOptionPB> options = List.from(allOptions);
|
||||
Option<String> createOption = filter;
|
||||
String? createOption = filter;
|
||||
|
||||
filter.foldRight(null, (filter, previous) {
|
||||
if (filter.isNotEmpty) {
|
||||
options.retainWhere((option) {
|
||||
final name = option.name.toLowerCase();
|
||||
final lFilter = filter.toLowerCase();
|
||||
if (filter != null && filter.isNotEmpty) {
|
||||
options.retainWhere((option) {
|
||||
final name = option.name.toLowerCase();
|
||||
final lFilter = filter.toLowerCase();
|
||||
|
||||
if (name == lFilter) {
|
||||
createOption = none();
|
||||
}
|
||||
if (name == lFilter) {
|
||||
createOption = null;
|
||||
}
|
||||
|
||||
return name.contains(lFilter);
|
||||
});
|
||||
} else {
|
||||
createOption = none();
|
||||
}
|
||||
});
|
||||
return name.contains(lFilter);
|
||||
});
|
||||
} else {
|
||||
createOption = null;
|
||||
}
|
||||
|
||||
return _MakeOptionResult(
|
||||
options: options,
|
||||
@@ -295,8 +291,8 @@ class SelectOptionEditorState with _$SelectOptionEditorState {
|
||||
required List<SelectOptionPB> options,
|
||||
required List<SelectOptionPB> allOptions,
|
||||
required List<SelectOptionPB> selectedOptions,
|
||||
required Option<String> createOption,
|
||||
required Option<String> filter,
|
||||
required String? createOption,
|
||||
required String? filter,
|
||||
}) = _SelectOptionEditorState;
|
||||
|
||||
factory SelectOptionEditorState.initial(SelectOptionCellController context) {
|
||||
@@ -305,8 +301,8 @@ class SelectOptionEditorState with _$SelectOptionEditorState {
|
||||
options: data?.options ?? [],
|
||||
allOptions: data?.options ?? [],
|
||||
selectedOptions: data?.selectOptions ?? [],
|
||||
createOption: none(),
|
||||
filter: none(),
|
||||
createOption: null,
|
||||
filter: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -318,5 +314,5 @@ class _MakeOptionResult {
|
||||
});
|
||||
|
||||
List<SelectOptionPB> options;
|
||||
Option<String> createOption;
|
||||
String? createOption;
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import 'cell_controller.dart';
|
||||
|
||||
/// CellMemCache is used to cache cell data of each block.
|
||||
/// We use CellContext to index the cell in the cache.
|
||||
/// Read https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid
|
||||
/// Read https://docs.appflowy.io/docs/documentation/software-contributions/architecture/frontend/frontend/grid
|
||||
/// for more information
|
||||
class CellMemCache {
|
||||
CellMemCache();
|
||||
|
||||
+4
-5
@@ -2,22 +2,21 @@ import 'dart:async';
|
||||
|
||||
import 'package:appflowy/plugins/database/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database/application/field/field_info.dart';
|
||||
import 'package:appflowy/plugins/database/application/field/field_listener.dart';
|
||||
import 'package:appflowy/plugins/database/domain/cell_listener.dart';
|
||||
import 'package:appflowy/plugins/database/domain/field_listener.dart';
|
||||
import 'package:appflowy/plugins/database/application/field/type_option/type_option_data_parser.dart';
|
||||
import 'package:appflowy/plugins/database/application/row/row_cache.dart';
|
||||
import 'package:appflowy/plugins/database/application/row/row_meta_listener.dart';
|
||||
import 'package:appflowy/plugins/database/domain/row_meta_listener.dart';
|
||||
import 'package:appflowy/plugins/database/application/row/row_service.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import 'cell_cache.dart';
|
||||
import 'cell_data_loader.dart';
|
||||
import 'cell_data_persistence.dart';
|
||||
import 'cell_listener.dart';
|
||||
|
||||
part 'cell_controller.freezed.dart';
|
||||
|
||||
@@ -173,7 +172,7 @@ class CellController<T, D> {
|
||||
Future<void> saveCellData(
|
||||
D data, {
|
||||
bool debounce = false,
|
||||
void Function(Option<FlowyError>)? onFinish,
|
||||
void Function(FlowyError?)? onFinish,
|
||||
}) async {
|
||||
_loadDataOperation?.cancel();
|
||||
if (debounce) {
|
||||
|
||||
+17
-3
@@ -6,7 +6,7 @@ import 'cell_data_loader.dart';
|
||||
import 'cell_data_persistence.dart';
|
||||
|
||||
typedef TextCellController = CellController<String, String>;
|
||||
typedef CheckboxCellController = CellController<String, String>;
|
||||
typedef CheckboxCellController = CellController<CheckboxCellDataPB, String>;
|
||||
typedef NumberCellController = CellController<String, String>;
|
||||
typedef SelectOptionCellController
|
||||
= CellController<SelectOptionCellDataPB, String>;
|
||||
@@ -14,6 +14,7 @@ typedef ChecklistCellController = CellController<ChecklistCellDataPB, String>;
|
||||
typedef DateCellController = CellController<DateCellDataPB, String>;
|
||||
typedef TimestampCellController = CellController<TimestampCellDataPB, String>;
|
||||
typedef URLCellController = CellController<URLCellDataPB, String>;
|
||||
typedef RelationCellController = CellController<RelationCellDataPB, String>;
|
||||
|
||||
CellController makeCellController(
|
||||
DatabaseController databaseController,
|
||||
@@ -24,13 +25,13 @@ CellController makeCellController(
|
||||
final fieldType = fieldController.getField(cellContext.fieldId)!.fieldType;
|
||||
switch (fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
return TextCellController(
|
||||
return CheckboxCellController(
|
||||
viewId: viewId,
|
||||
fieldController: fieldController,
|
||||
cellContext: cellContext,
|
||||
rowCache: rowCache,
|
||||
cellDataLoader: CellDataLoader(
|
||||
parser: StringCellDataParser(),
|
||||
parser: CheckboxCellDataParser(),
|
||||
),
|
||||
cellDataPersistence: TextCellDataPersistence(),
|
||||
);
|
||||
@@ -118,6 +119,19 @@ CellController makeCellController(
|
||||
),
|
||||
cellDataPersistence: TextCellDataPersistence(),
|
||||
);
|
||||
|
||||
case FieldType.Relation:
|
||||
return RelationCellController(
|
||||
viewId: viewId,
|
||||
fieldController: fieldController,
|
||||
cellContext: cellContext,
|
||||
rowCache: rowCache,
|
||||
cellDataLoader: CellDataLoader(
|
||||
parser: RelationCellDataParser(),
|
||||
reloadOnFieldChange: true,
|
||||
),
|
||||
cellDataPersistence: TextCellDataPersistence(),
|
||||
);
|
||||
}
|
||||
throw UnimplementedError;
|
||||
}
|
||||
+18
-1
@@ -1,10 +1,10 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:appflowy/plugins/database/domain/cell_service.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
|
||||
import 'cell_controller.dart';
|
||||
import 'cell_service.dart';
|
||||
|
||||
abstract class IGridCellDataConfig {
|
||||
// The cell data will reload if it receives the field's change notification.
|
||||
@@ -66,6 +66,16 @@ class StringCellDataParser implements CellDataParser<String> {
|
||||
}
|
||||
}
|
||||
|
||||
class CheckboxCellDataParser implements CellDataParser<CheckboxCellDataPB> {
|
||||
@override
|
||||
CheckboxCellDataPB? parserData(List<int> data) {
|
||||
if (data.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
return CheckboxCellDataPB.fromBuffer(data);
|
||||
}
|
||||
}
|
||||
|
||||
class NumberCellDataParser implements CellDataParser<String> {
|
||||
@override
|
||||
String? parserData(List<int> data) {
|
||||
@@ -123,3 +133,10 @@ class URLCellDataParser implements CellDataParser<URLCellDataPB> {
|
||||
return URLCellDataPB.fromBuffer(data);
|
||||
}
|
||||
}
|
||||
|
||||
class RelationCellDataParser implements CellDataParser<RelationCellDataPB> {
|
||||
@override
|
||||
RelationCellDataPB? parserData(List<int> data) {
|
||||
return data.isEmpty ? null : RelationCellDataPB.fromBuffer(data);
|
||||
}
|
||||
}
|
||||
+5
-6
@@ -1,13 +1,12 @@
|
||||
import 'package:appflowy/plugins/database/domain/cell_service.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/protobuf.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
|
||||
import 'cell_controller.dart';
|
||||
import 'cell_service.dart';
|
||||
|
||||
/// Save the cell data to disk
|
||||
/// You can extend this class to do custom operations.
|
||||
abstract class CellDataPersistence<D> {
|
||||
Future<Option<FlowyError>> save({
|
||||
Future<FlowyError?> save({
|
||||
required String viewId,
|
||||
required CellContext cellContext,
|
||||
required D data,
|
||||
@@ -18,7 +17,7 @@ class TextCellDataPersistence implements CellDataPersistence<String> {
|
||||
TextCellDataPersistence();
|
||||
|
||||
@override
|
||||
Future<Option<FlowyError>> save({
|
||||
Future<FlowyError?> save({
|
||||
required String viewId,
|
||||
required CellContext cellContext,
|
||||
required String data,
|
||||
@@ -30,8 +29,8 @@ class TextCellDataPersistence implements CellDataPersistence<String> {
|
||||
);
|
||||
return fut.then((result) {
|
||||
return result.fold(
|
||||
(l) => none(),
|
||||
(err) => Some(err),
|
||||
(l) => null,
|
||||
(err) => err,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
+15
-20
@@ -1,26 +1,21 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy/plugins/database/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database/application/view/view_cache.dart';
|
||||
import 'package:appflowy/plugins/database/domain/database_view_service.dart';
|
||||
import 'package:appflowy/plugins/database/domain/group_listener.dart';
|
||||
import 'package:appflowy/plugins/database/domain/layout_service.dart';
|
||||
import 'package:appflowy/plugins/database/domain/layout_setting_listener.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/board_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/calendar_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/database_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/group.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/group_changeset.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'dart:async';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'database_view_service.dart';
|
||||
import 'defines.dart';
|
||||
import 'layout/layout_service.dart';
|
||||
import 'layout/layout_setting_listener.dart';
|
||||
import 'row/row_cache.dart';
|
||||
import 'group/group_listener.dart';
|
||||
|
||||
typedef OnGroupConfigurationChanged = void Function(List<GroupSettingPB>);
|
||||
typedef OnGroupByField = void Function(List<GroupPB>);
|
||||
@@ -136,7 +131,7 @@ class DatabaseController {
|
||||
}
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> open() async {
|
||||
Future<FlowyResult<void, FlowyError>> open() async {
|
||||
return _databaseViewBackendSvc.openDatabase().then((result) {
|
||||
return result.fold(
|
||||
(DatabasePB database) async {
|
||||
@@ -157,21 +152,21 @@ class DatabaseController {
|
||||
return Future(() async {
|
||||
await _loadGroups();
|
||||
await _loadLayoutSetting();
|
||||
return left(fields);
|
||||
return FlowyResult.success(fields);
|
||||
});
|
||||
},
|
||||
(err) {
|
||||
Log.error(err);
|
||||
return right(err);
|
||||
return FlowyResult.failure(err);
|
||||
},
|
||||
);
|
||||
},
|
||||
(err) => right(err),
|
||||
(err) => FlowyResult.failure(err),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> moveGroupRow({
|
||||
Future<FlowyResult<void, FlowyError>> moveGroupRow({
|
||||
required RowMetaPB fromRow,
|
||||
required String fromGroupId,
|
||||
required String toGroupId,
|
||||
@@ -185,7 +180,7 @@ class DatabaseController {
|
||||
);
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> moveRow({
|
||||
Future<FlowyResult<void, FlowyError>> moveRow({
|
||||
required String fromRowId,
|
||||
required String toRowId,
|
||||
}) {
|
||||
@@ -195,7 +190,7 @@ class DatabaseController {
|
||||
);
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> moveGroup({
|
||||
Future<FlowyResult<void, FlowyError>> moveGroup({
|
||||
required String fromGroupId,
|
||||
required String toGroupId,
|
||||
}) {
|
||||
|
||||
@@ -5,7 +5,7 @@ import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/filte
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/sort/sort_info.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/database_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import 'field/field_info.dart';
|
||||
@@ -38,7 +38,7 @@ class LoadingState with _$LoadingState {
|
||||
const factory LoadingState.idle() = _Idle;
|
||||
const factory LoadingState.loading() = _Loading;
|
||||
const factory LoadingState.finish(
|
||||
Either<Unit, FlowyError> successOrFail,
|
||||
FlowyResult<void, FlowyError> successOrFail,
|
||||
) = _Finish;
|
||||
|
||||
const LoadingState._();
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:appflowy/plugins/database/application/field_settings/field_settings_service.dart';
|
||||
import 'package:appflowy/plugins/database/domain/field_settings_service.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
|
||||
+63
-38
@@ -1,30 +1,26 @@
|
||||
import 'package:appflowy/plugins/database/application/database_view_service.dart';
|
||||
import 'package:appflowy/plugins/database/application/field_settings/field_settings_listener.dart';
|
||||
import 'package:appflowy/plugins/database/application/field_settings/field_settings_service.dart';
|
||||
import 'package:appflowy/plugins/database/application/filter/filter_listener.dart';
|
||||
import 'package:appflowy/plugins/database/application/filter/filter_service.dart';
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:appflowy/plugins/database/domain/database_view_service.dart';
|
||||
import 'package:appflowy/plugins/database/domain/field_listener.dart';
|
||||
import 'package:appflowy/plugins/database/domain/field_settings_listener.dart';
|
||||
import 'package:appflowy/plugins/database/domain/field_settings_service.dart';
|
||||
import 'package:appflowy/plugins/database/domain/filter_listener.dart';
|
||||
import 'package:appflowy/plugins/database/domain/filter_service.dart';
|
||||
import 'package:appflowy/plugins/database/application/row/row_cache.dart';
|
||||
import 'package:appflowy/plugins/database/application/setting/setting_listener.dart';
|
||||
import 'package:appflowy/plugins/database/application/sort/sort_listener.dart';
|
||||
import 'package:appflowy/plugins/database/application/sort/sort_service.dart';
|
||||
import 'package:appflowy/plugins/database/domain/sort_listener.dart';
|
||||
import 'package:appflowy/plugins/database/domain/sort_service.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/filter_info.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/sort/sort_info.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/filter_changeset.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/group.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/sort_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/util.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../setting/setting_service.dart';
|
||||
import 'field_info.dart';
|
||||
import 'field_listener.dart';
|
||||
|
||||
class _GridFieldNotifier extends ChangeNotifier {
|
||||
List<FieldInfo> _fieldInfos = [];
|
||||
@@ -287,7 +283,7 @@ class FieldController {
|
||||
final fieldInfo = _findFieldInfo(
|
||||
fieldInfos: fieldInfos,
|
||||
fieldId: newSortPB.sort.fieldId,
|
||||
fieldType: newSortPB.sort.fieldType,
|
||||
fieldType: null,
|
||||
);
|
||||
|
||||
if (fieldInfo != null) {
|
||||
@@ -316,7 +312,7 @@ class FieldController {
|
||||
final fieldInfo = _findFieldInfo(
|
||||
fieldInfos: fieldInfos,
|
||||
fieldId: updatedSort.fieldId,
|
||||
fieldType: updatedSort.fieldType,
|
||||
fieldType: null,
|
||||
);
|
||||
|
||||
if (fieldInfo != null) {
|
||||
@@ -333,6 +329,32 @@ class FieldController {
|
||||
}
|
||||
}
|
||||
|
||||
void updateFieldInfos(
|
||||
List<SortInfo> newSortInfos,
|
||||
SortChangesetNotificationPB changeset,
|
||||
) {
|
||||
final changedFieldIds = HashSet<String>.from([
|
||||
...changeset.insertSorts.map((sort) => sort.sort.fieldId),
|
||||
...changeset.updateSorts.map((sort) => sort.fieldId),
|
||||
...changeset.deleteSorts.map((sort) => sort.fieldId),
|
||||
]);
|
||||
|
||||
final newFieldInfos = [...fieldInfos];
|
||||
|
||||
for (final fieldId in changedFieldIds) {
|
||||
final index =
|
||||
newFieldInfos.indexWhere((fieldInfo) => fieldInfo.id == fieldId);
|
||||
if (index == -1) {
|
||||
continue;
|
||||
}
|
||||
newFieldInfos[index] = newFieldInfos[index].copyWith(
|
||||
hasSort: newSortInfos.any((sort) => sort.fieldId == fieldId),
|
||||
);
|
||||
}
|
||||
|
||||
_fieldNotifier.fieldInfos = newFieldInfos;
|
||||
}
|
||||
|
||||
_sortsListener.start(
|
||||
onSortChanged: (result) {
|
||||
if (_isDisposed) {
|
||||
@@ -346,6 +368,7 @@ class FieldController {
|
||||
updateSortFromChangeset(newSortInfos, changeset);
|
||||
|
||||
_sortNotifier?.sorts = newSortInfos;
|
||||
updateFieldInfos(newSortInfos, changeset);
|
||||
},
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
@@ -353,7 +376,7 @@ class FieldController {
|
||||
);
|
||||
}
|
||||
|
||||
/// Listen for databse setting changes in the backend.
|
||||
/// Listen for database setting changes in the backend.
|
||||
void _listenOnSettingChanges() {
|
||||
_settingListener.start(
|
||||
onSettingUpdated: (result) {
|
||||
@@ -552,7 +575,7 @@ class FieldController {
|
||||
}
|
||||
|
||||
/// Load all of the fields. This is required when opening the database
|
||||
Future<Either<Unit, FlowyError>> loadFields({
|
||||
Future<FlowyResult<void, FlowyError>> loadFields({
|
||||
required List<FieldIdPB> fieldIds,
|
||||
}) async {
|
||||
final result = await _databaseViewBackendSvc.getFields(fieldIds: fieldIds);
|
||||
@@ -560,7 +583,7 @@ class FieldController {
|
||||
() => result.fold(
|
||||
(newFields) async {
|
||||
if (_isDisposed) {
|
||||
return left(unit);
|
||||
return FlowyResult.success(null);
|
||||
}
|
||||
|
||||
_fieldNotifier.fieldInfos =
|
||||
@@ -573,54 +596,54 @@ class FieldController {
|
||||
]);
|
||||
_updateFieldInfos();
|
||||
|
||||
return left(unit);
|
||||
return FlowyResult.success(null);
|
||||
},
|
||||
(err) => right(err),
|
||||
(err) => FlowyResult.failure(err),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Load all the filters from the backend. Required by `loadFields`
|
||||
Future<Either<Unit, FlowyError>> _loadFilters() async {
|
||||
Future<FlowyResult<void, FlowyError>> _loadFilters() async {
|
||||
return _filterBackendSvc.getAllFilters().then((result) {
|
||||
return result.fold(
|
||||
(filterPBs) {
|
||||
_filterNotifier?.filters = _filterInfoListFromPBs(filterPBs);
|
||||
return left(unit);
|
||||
return FlowyResult.success(null);
|
||||
},
|
||||
(err) => right(err),
|
||||
(err) => FlowyResult.failure(err),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Load all the sorts from the backend. Required by `loadFields`
|
||||
Future<Either<Unit, FlowyError>> _loadSorts() async {
|
||||
Future<FlowyResult<void, FlowyError>> _loadSorts() async {
|
||||
return _sortBackendSvc.getAllSorts().then((result) {
|
||||
return result.fold(
|
||||
(sortPBs) {
|
||||
_sortNotifier?.sorts = _sortInfoListFromPBs(sortPBs);
|
||||
return left(unit);
|
||||
return FlowyResult.success(null);
|
||||
},
|
||||
(err) => right(err),
|
||||
(err) => FlowyResult.failure(err),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Load all the field settings from the backend. Required by `loadFields`
|
||||
Future<Either<Unit, FlowyError>> _loadAllFieldSettings() async {
|
||||
Future<FlowyResult<void, FlowyError>> _loadAllFieldSettings() async {
|
||||
return _fieldSettingsBackendSvc.getAllFieldSettings().then((result) {
|
||||
return result.fold(
|
||||
(fieldSettingsList) {
|
||||
_fieldSettings.clear();
|
||||
_fieldSettings.addAll(fieldSettingsList);
|
||||
return left(unit);
|
||||
return FlowyResult.success(null);
|
||||
},
|
||||
(err) => right(err),
|
||||
(err) => FlowyResult.failure(err),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> _loadSettings() async {
|
||||
Future<FlowyResult<void, FlowyError>> _loadSettings() async {
|
||||
return SettingBackendService(viewId: viewId).getSetting().then(
|
||||
(result) => result.fold(
|
||||
(setting) {
|
||||
@@ -629,9 +652,9 @@ class FieldController {
|
||||
_groupConfigurationByFieldId[configuration.fieldId] =
|
||||
configuration;
|
||||
}
|
||||
return left(unit);
|
||||
return FlowyResult.success(null);
|
||||
},
|
||||
(err) => right(err),
|
||||
(err) => FlowyResult.failure(err),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -659,7 +682,7 @@ class FieldController {
|
||||
final fieldInfo = _findFieldInfo(
|
||||
fieldInfos: fieldInfos,
|
||||
fieldId: sortPB.fieldId,
|
||||
fieldType: sortPB.fieldType,
|
||||
fieldType: null,
|
||||
);
|
||||
return fieldInfo != null
|
||||
? SortInfo(sortPB: sortPB, fieldInfo: fieldInfo)
|
||||
@@ -818,9 +841,11 @@ class RowCacheDependenciesImpl extends RowFieldsDelegate with RowLifeCycle {
|
||||
FieldInfo? _findFieldInfo({
|
||||
required List<FieldInfo> fieldInfos,
|
||||
required String fieldId,
|
||||
required FieldType fieldType,
|
||||
required FieldType? fieldType,
|
||||
}) {
|
||||
return fieldInfos.firstWhereOrNull(
|
||||
(element) => element.id == fieldId && element.fieldType == fieldType,
|
||||
(element) =>
|
||||
element.id == fieldId &&
|
||||
(fieldType == null || element.fieldType == fieldType),
|
||||
);
|
||||
}
|
||||
+5
-5
@@ -1,17 +1,17 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:appflowy/plugins/database/application/field_settings/field_settings_service.dart';
|
||||
import 'package:appflowy/plugins/database/domain/field_listener.dart';
|
||||
import 'package:appflowy/plugins/database/domain/field_service.dart';
|
||||
import 'package:appflowy/plugins/database/domain/field_settings_service.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import 'field_controller.dart';
|
||||
import 'field_info.dart';
|
||||
import 'field_listener.dart';
|
||||
import 'field_service.dart';
|
||||
|
||||
part 'field_editor_bloc.freezed.dart';
|
||||
|
||||
@@ -111,7 +111,7 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
|
||||
);
|
||||
}
|
||||
|
||||
void _logIfError(Either<Unit, FlowyError> result) {
|
||||
void _logIfError(FlowyResult<void, FlowyError> result) {
|
||||
result.fold(
|
||||
(l) => null,
|
||||
(err) => Log.error(err),
|
||||
|
||||
@@ -47,9 +47,12 @@ class FieldInfo with _$FieldInfo {
|
||||
}
|
||||
|
||||
bool get canCreateFilter {
|
||||
if (hasFilter) return false;
|
||||
if (hasFilter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (field.fieldType) {
|
||||
case FieldType.Number:
|
||||
case FieldType.Checkbox:
|
||||
case FieldType.MultiSelect:
|
||||
case FieldType.RichText:
|
||||
@@ -62,7 +65,9 @@ class FieldInfo with _$FieldInfo {
|
||||
}
|
||||
|
||||
bool get canCreateSort {
|
||||
if (hasSort) return false;
|
||||
if (hasSort) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (field.fieldType) {
|
||||
case FieldType.RichText:
|
||||
@@ -73,6 +78,7 @@ class FieldInfo with _$FieldInfo {
|
||||
case FieldType.MultiSelect:
|
||||
case FieldType.LastEditedTime:
|
||||
case FieldType.CreatedTime:
|
||||
case FieldType.Checklist:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
||||
+4
-5
@@ -1,5 +1,4 @@
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/select_option_entities.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
@@ -28,10 +27,10 @@ class SelectOptionTypeOptionBloc
|
||||
emit(state.copyWith(options: options));
|
||||
},
|
||||
addingOption: () {
|
||||
emit(state.copyWith(isEditingOption: true, newOptionName: none()));
|
||||
emit(state.copyWith(isEditingOption: true, newOptionName: null));
|
||||
},
|
||||
endAddingOption: () {
|
||||
emit(state.copyWith(isEditingOption: false, newOptionName: none()));
|
||||
emit(state.copyWith(isEditingOption: false, newOptionName: null));
|
||||
},
|
||||
updateOption: (option) {
|
||||
final List<SelectOptionPB> options =
|
||||
@@ -69,13 +68,13 @@ class SelectOptionTypeOptionState with _$SelectOptionTypeOptionState {
|
||||
const factory SelectOptionTypeOptionState({
|
||||
required List<SelectOptionPB> options,
|
||||
required bool isEditingOption,
|
||||
required Option<String> newOptionName,
|
||||
required String? newOptionName,
|
||||
}) = _SelectOptionTypeOptionState;
|
||||
|
||||
factory SelectOptionTypeOptionState.initial(List<SelectOptionPB> options) =>
|
||||
SelectOptionTypeOptionState(
|
||||
options: options,
|
||||
isEditingOption: false,
|
||||
newOptionName: none(),
|
||||
newOptionName: null,
|
||||
);
|
||||
}
|
||||
Loaded 100 of 869 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user