Compare commits
135
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 | ||
|
|
b356927e60 | ||
|
|
1894409dc3 | ||
|
|
f29f1bae32 | ||
|
|
45054e7639 | ||
|
|
3b51a6e6be | ||
|
|
747fe40648 | ||
|
|
60fc5bb2c8 | ||
|
|
9d71464f1a | ||
|
|
92ceb2cd7d | ||
|
|
0db21b91a3 | ||
|
|
88e9d63a13 | ||
|
|
3129fa6cc1 | ||
|
|
db5372c18e | ||
|
|
b781c9aa0f | ||
|
|
a9c6b80f4a | ||
|
|
271f781cce | ||
|
|
ef4bce25d8 | ||
|
|
95c4cb241a | ||
|
|
e20d938ce3 | ||
|
|
a515715543 | ||
|
|
fda70ff560 | ||
|
|
08938b8c70 | ||
|
|
250f29f325 | ||
|
|
d117cd6b5b | ||
|
|
ca93cb20ec | ||
|
|
8c1d0106dd | ||
|
|
5cbc8b1e18 | ||
|
|
274742e334 | ||
|
|
792f56db79 | ||
|
|
458d3f971b | ||
|
|
2ffd6bf7ea | ||
|
|
722691c922 | ||
|
|
9746852b5f | ||
|
|
5b030303a6 | ||
|
|
1e4f342fc3 | ||
|
|
4f0379bdd7 |
No files matched your search
@@ -1,99 +0,0 @@
|
||||
name: Build AppFlowy Release APK
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.18.0-0.2.pre"
|
||||
RUST_TOOLCHAIN: "1.75"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: aarch64-linux-android
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
# the following step is required to avoid running out of space
|
||||
- name: Maximize build space
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
sudo rm -rf /usr/share/dotnet
|
||||
sudo rm -rf /opt/ghc
|
||||
sudo rm -rf "/usr/local/share/boost"
|
||||
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
||||
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust toolchain
|
||||
id: rust_toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
profile: minimal
|
||||
|
||||
- name: Install flutter
|
||||
id: flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "beta"
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
cache: true
|
||||
|
||||
- uses: nttld/setup-ndk@v1
|
||||
id: setup-ndk
|
||||
with:
|
||||
ndk-version: "r24"
|
||||
add-to-path: true
|
||||
|
||||
- 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"
|
||||
|
||||
- name: Install prerequisites
|
||||
working-directory: frontend
|
||||
run: |
|
||||
rustup target install aarch64-linux-android
|
||||
rustup target install x86_64-linux-android
|
||||
cargo install --force duckscript_cli
|
||||
cargo install cargo-ndk
|
||||
if [ "$RUNNER_OS" == "Linux" ]; then
|
||||
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
|
||||
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev
|
||||
sudo apt-get install keybinder-3.0 libnotify-dev
|
||||
sudo apt-get install gcc-multilib
|
||||
elif [ "$RUNNER_OS" == "Windows" ]; then
|
||||
vcpkg integrate install
|
||||
elif [ "$RUNNER_OS" == "macOS" ]; then
|
||||
echo 'do nothing'
|
||||
fi
|
||||
cargo make appflowy-flutter-deps-tools
|
||||
shell: bash
|
||||
|
||||
- name: Build AppFlowy
|
||||
working-directory: frontend
|
||||
env:
|
||||
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||
run: |
|
||||
cargo make --profile production-android appflowy-android
|
||||
|
||||
- name: Upload APK
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: app-arm64-v8a-release.apk
|
||||
path: frontend/appflowy_flutter/build/app/outputs/flutter-apk/
|
||||
@@ -0,0 +1,28 @@
|
||||
name: Build Bot
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
dispatch_slash_command:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# get build name from pubspec.yaml
|
||||
- name: Get build version
|
||||
working-directory: frontend/appflowy_flutter
|
||||
id: get_build_name
|
||||
run: |
|
||||
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 }}
|
||||
commands: build
|
||||
static-args: |
|
||||
ref=refs/pull/${{ github.event.issue.number }}/head
|
||||
build_name=${{ steps.get_build_name.outputs.build_name }}
|
||||
@@ -0,0 +1,42 @@
|
||||
name: build
|
||||
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [build-command]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: notify appflowy_builder
|
||||
run: |
|
||||
platform=${{ github.event.client_payload.slash_command.args.unnamed.arg1 }}
|
||||
build_name=${{ github.event.client_payload.slash_command.args.named.build_name }}
|
||||
branch=${{ github.event.client_payload.slash_command.args.named.ref }}
|
||||
build_type=""
|
||||
arch=""
|
||||
|
||||
if [ "$platform" = "android" ]; then
|
||||
build_type="apk"
|
||||
elif [ "$platform" = "macos" ]; then
|
||||
arch="universal"
|
||||
fi
|
||||
|
||||
params=$(jq -n \
|
||||
--arg ref "main" \
|
||||
--arg repo "LucasXu0/AppFlowy" \
|
||||
--arg branch "$branch" \
|
||||
--arg build_name "$build_name" \
|
||||
--arg build_type "$build_type" \
|
||||
--arg arch "$arch" \
|
||||
'{ref: $ref, inputs: {repo: $repo, branch: $branch, build_name: $build_name, build_type: $build_type, arch: $arch}} | del(.inputs | .. | select(. == ""))')
|
||||
|
||||
echo "params: $params"
|
||||
|
||||
curl -L \
|
||||
-X POST \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Authorization: Bearer ${{ secrets.TOKEN }}" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
https://api.github.com/repos/AppFlowy-IO/AppFlowy-Builder/actions/workflows/$platform.yaml/dispatches \
|
||||
-d "$params"
|
||||
@@ -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,8 +18,7 @@ on:
|
||||
- "!frontend/appflowy_tauri/**"
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.18.0-0.2.pre"
|
||||
RUST_TOOLCHAIN: "1.75"
|
||||
FLUTTER_VERSION: "3.19.0"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
@@ -38,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: |
|
||||
@@ -46,43 +55,34 @@ jobs:
|
||||
sudo rm -rf /opt/ghc
|
||||
sudo rm -rf "/usr/local/share/boost"
|
||||
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
||||
sudo docker image prune --all --force
|
||||
sudo rm -rf /opt/hostedtoolcache/codeQL
|
||||
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
|
||||
|
||||
- name: Install Rust toolchain
|
||||
id: rust_toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
profile: minimal
|
||||
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
|
||||
with:
|
||||
ndk-version: "r24"
|
||||
add-to-path: true
|
||||
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
|
||||
@@ -327,6 +327,8 @@ jobs:
|
||||
LINUX_PACKAGE_DEB_NAME: AppFlowy-${{ github.ref_name }}-linux-x86_64.deb
|
||||
LINUX_PACKAGE_RPM_NAME: AppFlowy-${{ github.ref_name }}-linux-x86_64.rpm
|
||||
LINUX_PACKAGE_TMP_RPM_NAME: AppFlowy-${{ github.ref_name }}-2.x86_64.rpm
|
||||
LINUX_PACKAGE_TMP_APPIMAGE_NAME: AppFlowy-${{ github.ref_name }}-x86_64.AppImage
|
||||
LINUX_PACKAGE_APPIMAGE_NAME: AppFlowy-${{ github.ref_name }}-linux-x86_64.AppImage
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -341,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
|
||||
@@ -395,11 +397,15 @@ jobs:
|
||||
- name: Build Linux package (.rpm)
|
||||
working-directory: ${{ env.LINUX_APP_RELEASE_PATH }}
|
||||
run: |
|
||||
ls -a
|
||||
sudo alien -r ${{ env.LINUX_PACKAGE_DEB_NAME }}
|
||||
echo '------------'
|
||||
cp -r ${{ env.LINUX_PACKAGE_TMP_RPM_NAME }} ${{ env.LINUX_PACKAGE_RPM_NAME }}
|
||||
ls -a
|
||||
|
||||
- name: Build Linux package (.AppImage)
|
||||
working-directory: frontend
|
||||
continue-on-error: true
|
||||
run: |
|
||||
sh scripts/linux_distribution/appimage/build_appimage.sh ${{ github.ref_name }}
|
||||
cp -r ${{ env.LINUX_PACKAGE_TMP_APPIMAGE_NAME }} ${{ env.LINUX_PACKAGE_APPIMAGE_NAME }}
|
||||
|
||||
- name: Upload Asset
|
||||
id: upload-release-asset
|
||||
@@ -434,23 +440,34 @@ jobs:
|
||||
asset_name: ${{ env.LINUX_PACKAGE_RPM_NAME }}
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Upload AppImage package
|
||||
id: upload-release-asset-install-package-appimage
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ needs.create-release.outputs.upload_url }}
|
||||
asset_path: ${{ env.LINUX_APP_RELEASE_PATH }}/${{ env.LINUX_PACKAGE_APPIMAGE_NAME }}
|
||||
asset_name: ${{ env.LINUX_PACKAGE_APPIMAGE_NAME }}
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
build-for-docker:
|
||||
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 }}
|
||||
|
||||
@@ -1,4 +1,39 @@
|
||||
# Release Notes
|
||||
## 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
|
||||
|
||||
## Version 0.4.6 - 02/03/2024
|
||||
### Bug Fixes
|
||||
- Fixed refresh token bug
|
||||
|
||||
## Version 0.4.5 - 02/01/2024
|
||||
### Bug Fixes
|
||||
- Fixed WebSocket connection issue
|
||||
|
||||
## Version 0.4.4 - 01/31/2024
|
||||
### New Features
|
||||
- Added functionality for uploading images to cloud storage.
|
||||
|
||||
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.4"
|
||||
APPFLOWY_VERSION = "0.5.1"
|
||||
FLUTTER_DESKTOP_FEATURES = "dart,rev-sqlite"
|
||||
PRODUCT_NAME = "AppFlowy"
|
||||
MACOSX_DEPLOYMENT_TARGET = "11.0"
|
||||
@@ -50,6 +50,7 @@ APP_ENVIRONMENT = "local"
|
||||
FLUTTER_FLOWY_SDK_PATH = "appflowy_flutter/packages/appflowy_backend"
|
||||
TAURI_BACKEND_SERVICE_PATH = "appflowy_tauri/src/services/backend"
|
||||
WEB_BACKEND_SERVICE_PATH = "appflowy_web/src/services/backend"
|
||||
WEB_LIB_PATH= "appflowy_web/wasm-libs/af-wasm"
|
||||
# Test default config
|
||||
TEST_CRATE_TYPE = "cdylib"
|
||||
TEST_LIB_EXT = "dylib"
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -39,12 +39,7 @@ void main() {
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
|
||||
// Invoke the field editor
|
||||
await tester.tapGridFieldWithName('Type');
|
||||
await tester.tapEditFieldButton();
|
||||
|
||||
await tester.tapSwitchFieldTypeButton();
|
||||
await tester.selectFieldType(FieldType.Checkbox);
|
||||
await tester.dismissFieldEditor();
|
||||
await tester.changeFieldTypeOfFieldWithName('Type', FieldType.Checkbox);
|
||||
|
||||
await tester.assertFieldTypeWithFieldName(
|
||||
'Type',
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ void main() {
|
||||
|
||||
testWidgets('add checkbox sort', (tester) async {
|
||||
await tester.openV020database();
|
||||
// create a filter
|
||||
// create a sort
|
||||
await tester.tapDatabaseSortButton();
|
||||
await tester.tapCreateSortByFieldType(FieldType.Checkbox, 'Done');
|
||||
|
||||
@@ -136,7 +136,7 @@ void main() {
|
||||
|
||||
testWidgets('add number sort', (tester) async {
|
||||
await tester.openV020database();
|
||||
// create a filter
|
||||
// create a sort
|
||||
await tester.tapDatabaseSortButton();
|
||||
await tester.tapCreateSortByFieldType(FieldType.Number, 'number');
|
||||
|
||||
@@ -188,7 +188,7 @@ void main() {
|
||||
|
||||
testWidgets('add checkbox and number sort', (tester) async {
|
||||
await tester.openV020database();
|
||||
// create a filter
|
||||
// create a sort
|
||||
await tester.tapDatabaseSortButton();
|
||||
await tester.tapCreateSortByFieldType(FieldType.Checkbox, 'Done');
|
||||
|
||||
@@ -264,5 +264,111 @@ void main() {
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
testWidgets('reorder sort', (tester) async {
|
||||
await tester.openV020database();
|
||||
// create a sort
|
||||
await tester.tapDatabaseSortButton();
|
||||
await tester.tapCreateSortByFieldType(FieldType.Checkbox, 'Done');
|
||||
|
||||
// open the sort menu and sort checkbox by descending
|
||||
await tester.tapSortMenuInSettingBar();
|
||||
await tester.tapSortButtonByName('Done');
|
||||
await tester.tapSortByDescending();
|
||||
|
||||
// add another sort, this time by number descending
|
||||
await tester.tapSortMenuInSettingBar();
|
||||
await tester.tapCreateSortByFieldTypeInSortMenu(
|
||||
FieldType.Number,
|
||||
'number',
|
||||
);
|
||||
await tester.tapSortButtonByName('number');
|
||||
await tester.tapSortByDescending();
|
||||
|
||||
// check checkbox cell order
|
||||
for (final (index, content) in <bool>[
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
].indexed) {
|
||||
await tester.assertCheckboxCell(
|
||||
rowIndex: index,
|
||||
isSelected: content,
|
||||
);
|
||||
}
|
||||
|
||||
// check number cell order
|
||||
for (final (index, content) in <String>[
|
||||
'1',
|
||||
'0.2',
|
||||
'0.1',
|
||||
'-1',
|
||||
'-2',
|
||||
'12',
|
||||
'11',
|
||||
'10',
|
||||
'2',
|
||||
'',
|
||||
].indexed) {
|
||||
tester.assertCellContent(
|
||||
rowIndex: index,
|
||||
fieldType: FieldType.Number,
|
||||
content: content,
|
||||
);
|
||||
}
|
||||
|
||||
// reorder sort
|
||||
await tester.tapSortMenuInSettingBar();
|
||||
await tester.reorderSort(
|
||||
(FieldType.Number, 'number'),
|
||||
(FieldType.Checkbox, 'Done'),
|
||||
);
|
||||
|
||||
// check checkbox cell order
|
||||
for (final (index, content) in <bool>[
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
].indexed) {
|
||||
await tester.assertCheckboxCell(
|
||||
rowIndex: index,
|
||||
isSelected: content,
|
||||
);
|
||||
}
|
||||
|
||||
// check the number cell order
|
||||
for (final (index, content) in <String>[
|
||||
'12',
|
||||
'11',
|
||||
'10',
|
||||
'2',
|
||||
'1',
|
||||
'0.2',
|
||||
'0.1',
|
||||
'-1',
|
||||
'-2',
|
||||
'',
|
||||
].indexed) {
|
||||
tester.assertCellContent(
|
||||
rowIndex: index,
|
||||
fieldType: FieldType.Number,
|
||||
content: content,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
+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);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pbenum.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../util/database_test_op.dart';
|
||||
import '../util/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('Grid Calculations', () {
|
||||
testWidgets('add calculation and update cell', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
|
||||
// Change one Field to Number
|
||||
await tester.changeFieldTypeOfFieldWithName('Type', FieldType.Number);
|
||||
|
||||
expect(find.text('Calculate'), findsOneWidget);
|
||||
|
||||
await tester.changeCalculateAtIndex(1, CalculationType.Sum);
|
||||
|
||||
// Enter values in cells
|
||||
await tester.editCell(
|
||||
rowIndex: 0,
|
||||
fieldType: FieldType.Number,
|
||||
input: '100',
|
||||
);
|
||||
|
||||
await tester.editCell(
|
||||
rowIndex: 1,
|
||||
fieldType: FieldType.Number,
|
||||
input: '100',
|
||||
);
|
||||
|
||||
// Dismiss edit cell
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.enter);
|
||||
|
||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
|
||||
expect(find.text('200'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('add calculations and remove row', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
|
||||
// Change two Fields to Number
|
||||
await tester.changeFieldTypeOfFieldWithName('Type', FieldType.Number);
|
||||
await tester.changeFieldTypeOfFieldWithName('Done', FieldType.Number);
|
||||
|
||||
expect(find.text('Calculate'), findsNWidgets(2));
|
||||
|
||||
await tester.changeCalculateAtIndex(1, CalculationType.Sum);
|
||||
await tester.changeCalculateAtIndex(2, CalculationType.Min);
|
||||
|
||||
// Enter values in cells
|
||||
await tester.editCell(
|
||||
rowIndex: 0,
|
||||
fieldType: FieldType.Number,
|
||||
input: '100',
|
||||
);
|
||||
await tester.editCell(
|
||||
rowIndex: 1,
|
||||
fieldType: FieldType.Number,
|
||||
input: '150',
|
||||
);
|
||||
await tester.editCell(
|
||||
rowIndex: 0,
|
||||
fieldType: FieldType.Number,
|
||||
input: '50',
|
||||
cellIndex: 1,
|
||||
);
|
||||
await tester.editCell(
|
||||
rowIndex: 1,
|
||||
fieldType: FieldType.Number,
|
||||
input: '100',
|
||||
cellIndex: 1,
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Dismiss edit cell
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.enter);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('250'), findsOneWidget);
|
||||
expect(find.text('50'), findsNWidgets(2));
|
||||
|
||||
// Delete 1st row
|
||||
await tester.hoverOnFirstRowOfGrid();
|
||||
await tester.tapRowMenuButtonInGrid();
|
||||
await tester.tapDeleteOnRowMenu();
|
||||
|
||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
|
||||
expect(find.text('150'), findsNWidgets(2));
|
||||
expect(find.text('100'), findsNWidgets(2));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/plugins/database/application/calculations/calculation_type_ext.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/calculations/calculate_cell.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/calculations/calculation_type_item.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/common/type_option_separator.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/header/type_option/number.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/checkbox.dart';
|
||||
@@ -720,6 +723,31 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
await pumpAndSettle();
|
||||
}
|
||||
|
||||
Future<void> changeFieldTypeOfFieldWithName(
|
||||
String name,
|
||||
FieldType type,
|
||||
) async {
|
||||
await tapGridFieldWithName(name);
|
||||
await tapEditFieldButton();
|
||||
|
||||
await tapSwitchFieldTypeButton();
|
||||
await selectFieldType(type);
|
||||
await dismissFieldEditor();
|
||||
}
|
||||
|
||||
Future<void> changeCalculateAtIndex(int index, CalculationType type) async {
|
||||
await tap(find.byType(CalculateCell).at(index));
|
||||
await pumpAndSettle();
|
||||
|
||||
await tap(
|
||||
find.descendant(
|
||||
of: find.byType(CalculationTypeItem),
|
||||
matching: find.text(type.label),
|
||||
),
|
||||
);
|
||||
await pumpAndSettle();
|
||||
}
|
||||
|
||||
/// Should call [tapGridFieldWithName] first.
|
||||
Future<void> tapEditFieldButton() async {
|
||||
await tapButtonWithName(LocaleKeys.grid_field_editProperty.tr());
|
||||
@@ -1042,6 +1070,34 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
await tapButton(findSortItem);
|
||||
}
|
||||
|
||||
/// Must call [tapSortMenuInSettingBar] first.
|
||||
Future<void> reorderSort(
|
||||
(FieldType, String) from,
|
||||
(FieldType, String) to,
|
||||
) async {
|
||||
final fromSortItem = find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is DatabaseSortItem &&
|
||||
widget.sortInfo.fieldInfo.fieldType == from.$1 &&
|
||||
widget.sortInfo.fieldInfo.name == from.$2,
|
||||
);
|
||||
final toSortItem = find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is DatabaseSortItem &&
|
||||
widget.sortInfo.fieldInfo.fieldType == to.$1 &&
|
||||
widget.sortInfo.fieldInfo.name == to.$2,
|
||||
);
|
||||
final dragElement = find.descendant(
|
||||
of: fromSortItem,
|
||||
matching: find.byType(ReorderableDragStartListener),
|
||||
);
|
||||
await drag(
|
||||
dragElement,
|
||||
getCenter(toSortItem) - getCenter(fromSortItem),
|
||||
);
|
||||
await pumpAndSettle(const Duration(milliseconds: 200));
|
||||
}
|
||||
|
||||
/// Must call [tapSortButtonByName] first.
|
||||
Future<void> tapSortByDescending() async {
|
||||
await tapButton(
|
||||
@@ -1060,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),
|
||||
);
|
||||
}
|
||||
}
|
||||
+40
-41
@@ -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,13 +51,16 @@ 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);
|
||||
await useAppFlowyBetaCloudWithURL(
|
||||
kAppflowyCloudUrl,
|
||||
AuthenticatorType.appflowyCloud,
|
||||
);
|
||||
return AuthenticatorType.appflowyCloud;
|
||||
}
|
||||
|
||||
switch (value.getOrElse(() => "0")) {
|
||||
switch (value ?? "0") {
|
||||
case "0":
|
||||
return AuthenticatorType.local;
|
||||
case "1":
|
||||
@@ -70,7 +72,10 @@ Future<AuthenticatorType> getAuthenticatorType() async {
|
||||
case "4":
|
||||
return AuthenticatorType.appflowyCloudDevelop;
|
||||
default:
|
||||
await useAppFlowyBetaCloudWithURL(kAppflowyCloudUrl);
|
||||
await useAppFlowyBetaCloudWithURL(
|
||||
kAppflowyCloudUrl,
|
||||
AuthenticatorType.appflowyCloud,
|
||||
);
|
||||
return AuthenticatorType.appflowyCloud;
|
||||
}
|
||||
}
|
||||
@@ -171,21 +176,21 @@ 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(String url) async {
|
||||
await _setAuthenticatorType(AuthenticatorType.appflowyCloud);
|
||||
await _setAppFlowyCloudUrl(Some(url));
|
||||
Future<void> useAppFlowyBetaCloudWithURL(
|
||||
String url,
|
||||
AuthenticatorType authenticatorType,
|
||||
) async {
|
||||
await _setAuthenticatorType(authenticatorType);
|
||||
await _setAppFlowyCloudUrl(url);
|
||||
}
|
||||
|
||||
Future<void> useLocalServer() async {
|
||||
@@ -197,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.
|
||||
@@ -305,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 {
|
||||
@@ -330,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,
|
||||
@@ -358,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,28 +175,22 @@ class AppBarMoreButton extends StatelessWidget {
|
||||
class AppBarButton extends StatelessWidget {
|
||||
const AppBarButton({
|
||||
super.key,
|
||||
this.isActionButton = false,
|
||||
required this.onTap,
|
||||
required this.child,
|
||||
this.padding,
|
||||
});
|
||||
|
||||
static const defaultWidth = 40.0;
|
||||
|
||||
final VoidCallback onTap;
|
||||
final Widget child;
|
||||
final bool isActionButton;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
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
@@ -44,7 +44,6 @@ class _MobileBottomSheetEditLinkWidgetState
|
||||
void dispose() {
|
||||
textController.dispose();
|
||||
hrefController.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
+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,
|
||||
),
|
||||
|
||||
+5
-3
@@ -27,14 +27,16 @@ class _MobileBottomSheetRenameWidgetState
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
controller = TextEditingController(text: widget.name);
|
||||
controller = TextEditingController(text: widget.name)
|
||||
..selection = TextSelection(
|
||||
baseOffset: 0,
|
||||
extentOffset: widget.name.length,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
controller.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
+19
-27
@@ -1,7 +1,8 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/widget/separated_flex.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum MobileViewItemBottomSheetBodyAction {
|
||||
@@ -25,55 +26,46 @@ class MobileViewItemBottomSheetBody extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
return SeparatedColumn(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
separatorBuilder: () => const Divider(
|
||||
height: 8.5,
|
||||
thickness: 0.5,
|
||||
),
|
||||
children: [
|
||||
FlowyOptionTile.text(
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.button_rename.tr(),
|
||||
leftIcon: const FlowySvg(
|
||||
FlowySvgs.m_rename_s,
|
||||
),
|
||||
showTopBorder: false,
|
||||
icon: FlowySvgs.m_rename_s,
|
||||
onTap: () => onAction(
|
||||
MobileViewItemBottomSheetBodyAction.rename,
|
||||
),
|
||||
),
|
||||
FlowyOptionTile.text(
|
||||
MobileQuickActionButton(
|
||||
text: isFavorite
|
||||
? LocaleKeys.button_removeFromFavorites.tr()
|
||||
: LocaleKeys.button_addToFavorites.tr(),
|
||||
leftIcon: FlowySvg(
|
||||
size: const Size(20, 20),
|
||||
isFavorite
|
||||
? FlowySvgs.m_favorite_selected_lg
|
||||
: FlowySvgs.m_favorite_unselected_lg,
|
||||
color: isFavorite ? Colors.yellow : null,
|
||||
),
|
||||
showTopBorder: false,
|
||||
icon: isFavorite
|
||||
? FlowySvgs.m_favorite_selected_lg
|
||||
: FlowySvgs.m_favorite_unselected_lg,
|
||||
iconColor: isFavorite ? Colors.yellow : null,
|
||||
onTap: () => onAction(
|
||||
isFavorite
|
||||
? MobileViewItemBottomSheetBodyAction.removeFromFavorites
|
||||
: MobileViewItemBottomSheetBodyAction.addToFavorites,
|
||||
),
|
||||
),
|
||||
FlowyOptionTile.text(
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.button_duplicate.tr(),
|
||||
leftIcon: const FlowySvg(
|
||||
FlowySvgs.m_duplicate_s,
|
||||
),
|
||||
showTopBorder: false,
|
||||
icon: FlowySvgs.m_duplicate_s,
|
||||
onTap: () => onAction(
|
||||
MobileViewItemBottomSheetBodyAction.duplicate,
|
||||
),
|
||||
),
|
||||
FlowyOptionTile.text(
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.button_delete.tr(),
|
||||
textColor: Theme.of(context).colorScheme.error,
|
||||
leftIcon: FlowySvg(
|
||||
FlowySvgs.m_delete_s,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
showTopBorder: false,
|
||||
icon: FlowySvgs.m_delete_s,
|
||||
iconColor: Theme.of(context).colorScheme.error,
|
||||
onTap: () => onAction(
|
||||
MobileViewItemBottomSheetBodyAction.delete,
|
||||
),
|
||||
|
||||
+19
-27
@@ -1,9 +1,10 @@
|
||||
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/widgets.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum MobileViewBottomSheetBodyAction {
|
||||
@@ -84,55 +85,46 @@ class MobileViewBottomSheetBody extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isFavorite = view.isFavorite;
|
||||
return Column(
|
||||
return SeparatedColumn(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
separatorBuilder: () => const Divider(
|
||||
height: 8.5,
|
||||
thickness: 0.5,
|
||||
),
|
||||
children: [
|
||||
FlowyOptionTile.text(
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.button_rename.tr(),
|
||||
leftIcon: const FlowySvg(
|
||||
FlowySvgs.m_rename_s,
|
||||
),
|
||||
showTopBorder: false,
|
||||
icon: FlowySvgs.m_rename_s,
|
||||
onTap: () => onAction(
|
||||
MobileViewBottomSheetBodyAction.rename,
|
||||
),
|
||||
),
|
||||
FlowyOptionTile.text(
|
||||
MobileQuickActionButton(
|
||||
text: isFavorite
|
||||
? LocaleKeys.button_removeFromFavorites.tr()
|
||||
: LocaleKeys.button_addToFavorites.tr(),
|
||||
leftIcon: FlowySvg(
|
||||
size: const Size(20, 20),
|
||||
isFavorite
|
||||
? FlowySvgs.m_favorite_selected_lg
|
||||
: FlowySvgs.m_favorite_unselected_lg,
|
||||
color: isFavorite ? Colors.yellow : null,
|
||||
),
|
||||
showTopBorder: false,
|
||||
icon: isFavorite
|
||||
? FlowySvgs.m_favorite_selected_lg
|
||||
: FlowySvgs.m_favorite_unselected_lg,
|
||||
iconColor: isFavorite ? Colors.yellow : null,
|
||||
onTap: () => onAction(
|
||||
isFavorite
|
||||
? MobileViewBottomSheetBodyAction.removeFromFavorites
|
||||
: MobileViewBottomSheetBodyAction.addToFavorites,
|
||||
),
|
||||
),
|
||||
FlowyOptionTile.text(
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.button_duplicate.tr(),
|
||||
leftIcon: const FlowySvg(
|
||||
FlowySvgs.m_duplicate_s,
|
||||
),
|
||||
showTopBorder: false,
|
||||
icon: FlowySvgs.m_duplicate_s,
|
||||
onTap: () => onAction(
|
||||
MobileViewBottomSheetBodyAction.duplicate,
|
||||
),
|
||||
),
|
||||
FlowyOptionTile.text(
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.button_delete.tr(),
|
||||
textColor: Theme.of(context).colorScheme.error,
|
||||
leftIcon: FlowySvg(
|
||||
FlowySvgs.m_delete_s,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
showTopBorder: false,
|
||||
icon: FlowySvgs.m_delete_s,
|
||||
iconColor: Theme.of(context).colorScheme.error,
|
||||
onTap: () => onAction(
|
||||
MobileViewBottomSheetBodyAction.delete,
|
||||
),
|
||||
|
||||
+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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+6
@@ -38,6 +38,12 @@ class _MobileBoardContentState extends State<MobileBoardContent> {
|
||||
scrollController = ScrollController();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
|
||||
+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(
|
||||
|
||||
+6
@@ -19,6 +19,12 @@ class _MobileBoardTrailingState extends State<MobileBoardTrailing> {
|
||||
|
||||
bool isEditing = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_textController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenSize = MediaQuery.of(context).size;
|
||||
|
||||
+33
-42
@@ -81,6 +81,7 @@ class _MobileRowDetailPageState extends State<MobileRowDetailPage> {
|
||||
child: Scaffold(
|
||||
appBar: FlowyAppBar(
|
||||
leadingType: FlowyAppBarLeadingType.close,
|
||||
showDivider: false,
|
||||
actions: [
|
||||
AppBarMoreButton(
|
||||
onTap: (_) => _showCardActions(context),
|
||||
@@ -135,54 +136,44 @@ class _MobileRowDetailPageState extends State<MobileRowDetailPage> {
|
||||
builder: (_) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: MobileQuickActionButton(
|
||||
onTap: () {
|
||||
final rowId = _bloc.state.currentRowId;
|
||||
if (rowId == null) {
|
||||
return;
|
||||
}
|
||||
RowBackendService.duplicateRow(viewId, rowId);
|
||||
context
|
||||
..pop()
|
||||
..pop();
|
||||
Fluttertoast.showToast(
|
||||
msg: LocaleKeys.board_cardDuplicated.tr(),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
},
|
||||
icon: FlowySvgs.copy_s,
|
||||
text: LocaleKeys.button_duplicate.tr(),
|
||||
),
|
||||
MobileQuickActionButton(
|
||||
onTap: () =>
|
||||
_performAction(viewId, _bloc.state.currentRowId, false),
|
||||
icon: FlowySvgs.copy_s,
|
||||
text: LocaleKeys.button_duplicate.tr(),
|
||||
),
|
||||
const Divider(height: 9),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: MobileQuickActionButton(
|
||||
onTap: () {
|
||||
final rowId = _bloc.state.currentRowId;
|
||||
if (rowId == null) {
|
||||
return;
|
||||
}
|
||||
RowBackendService.deleteRow(viewId, rowId);
|
||||
context
|
||||
..pop()
|
||||
..pop();
|
||||
Fluttertoast.showToast(
|
||||
msg: LocaleKeys.board_cardDeleted.tr(),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
},
|
||||
icon: FlowySvgs.m_delete_m,
|
||||
text: LocaleKeys.button_delete.tr(),
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
const Divider(height: 8.5, thickness: 0.5),
|
||||
MobileQuickActionButton(
|
||||
onTap: () => _performAction(viewId, _bloc.state.currentRowId, true),
|
||||
text: LocaleKeys.button_delete.tr(),
|
||||
textColor: Theme.of(context).colorScheme.error,
|
||||
icon: FlowySvgs.m_delete_m,
|
||||
iconColor: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _performAction(String viewId, String? rowId, bool deleteRow) {
|
||||
if (rowId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
deleteRow
|
||||
? RowBackendService.deleteRow(viewId, rowId)
|
||||
: RowBackendService.duplicateRow(viewId, rowId);
|
||||
|
||||
context
|
||||
..pop()
|
||||
..pop();
|
||||
Fluttertoast.showToast(
|
||||
msg: deleteRow
|
||||
? LocaleKeys.board_cardDeleted.tr()
|
||||
: LocaleKeys.board_cardDuplicated.tr(),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RowDetailFab extends StatelessWidget {
|
||||
|
||||
+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
-39
@@ -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';
|
||||
@@ -196,7 +194,6 @@ class _MobileFieldEditorState extends State<MobileFieldEditor> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
values = widget.defaultValues;
|
||||
controller.text = values.name;
|
||||
}
|
||||
@@ -204,7 +201,6 @@ class _MobileFieldEditorState extends State<MobileFieldEditor> {
|
||||
@override
|
||||
void dispose() {
|
||||
controller.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -225,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) ...[
|
||||
@@ -446,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);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -713,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();
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+25
-9
@@ -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';
|
||||
@@ -22,15 +23,18 @@ class MobileDatabaseFieldList extends StatelessWidget {
|
||||
const MobileDatabaseFieldList({
|
||||
super.key,
|
||||
required this.databaseController,
|
||||
required this.canCreate,
|
||||
});
|
||||
|
||||
final DatabaseController databaseController;
|
||||
final bool canCreate;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _MobileDatabaseFieldListBody(
|
||||
databaseController: databaseController,
|
||||
viewId: context.read<ViewBloc>().state.view.id,
|
||||
canCreate: canCreate,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -39,10 +43,12 @@ class _MobileDatabaseFieldListBody extends StatelessWidget {
|
||||
const _MobileDatabaseFieldListBody({
|
||||
required this.databaseController,
|
||||
required this.viewId,
|
||||
required this.canCreate,
|
||||
});
|
||||
|
||||
final DatabaseController databaseController;
|
||||
final String viewId;
|
||||
final bool canCreate;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -56,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(
|
||||
@@ -63,7 +70,7 @@ class _MobileDatabaseFieldListBody extends StatelessWidget {
|
||||
viewId: viewId,
|
||||
fieldController: databaseController.fieldController,
|
||||
fieldInfo: firstField,
|
||||
showTopBorder: true,
|
||||
showTopBorder: false,
|
||||
);
|
||||
final cells = fields
|
||||
.mapIndexed(
|
||||
@@ -113,13 +120,22 @@ class _MobileDatabaseFieldListBody extends StatelessWidget {
|
||||
.add(DatabasePropertyEvent.moveField(from, to));
|
||||
},
|
||||
header: firstCell,
|
||||
footer: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_divider(),
|
||||
_NewDatabaseFieldTile(viewId: viewId),
|
||||
],
|
||||
),
|
||||
footer: canCreate
|
||||
? Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_divider(),
|
||||
_NewDatabaseFieldTile(viewId: viewId),
|
||||
VSpace(
|
||||
context.bottomSheetPadding(
|
||||
ignoreViewPadding: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: VSpace(
|
||||
context.bottomSheetPadding(ignoreViewPadding: false),
|
||||
),
|
||||
itemCount: cells.length,
|
||||
itemBuilder: (context, index) => cells[index],
|
||||
);
|
||||
@@ -197,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),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
+34
-28
@@ -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,20 +76,20 @@ class MobileDatabaseViewQuickActions extends StatelessWidget {
|
||||
Widget _actionButton(
|
||||
BuildContext context,
|
||||
_Action action,
|
||||
VoidCallback onTap,
|
||||
) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: MobileQuickActionButton(
|
||||
icon: action.icon,
|
||||
text: action.label,
|
||||
color: action.color(context),
|
||||
onTap: 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,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _divider() => const Divider(height: 9);
|
||||
Widget _divider() => const Divider(height: 8.5, thickness: 0.5);
|
||||
}
|
||||
|
||||
enum _Action {
|
||||
|
||||
+20
-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(),
|
||||
@@ -83,15 +82,23 @@ class _NameAndIconState extends State<_NameAndIcon> {
|
||||
textEditingController.text = widget.view.name;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
textEditingController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@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));
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -222,24 +229,18 @@ 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>(),
|
||||
child: MobileDatabaseFieldList(
|
||||
databaseController: databaseController,
|
||||
canCreate: true,
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -250,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),
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
// TODO(yijing): delete this after implementing the real screen inside bottom navigation bar.
|
||||
/// For demonstration purposes
|
||||
class DetailsPlaceholderScreen extends StatefulWidget {
|
||||
/// Constructs a [DetailsScreen].
|
||||
const DetailsPlaceholderScreen({
|
||||
required this.label,
|
||||
this.param,
|
||||
this.extra,
|
||||
this.withScaffold = true,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The label to display in the center of the screen.
|
||||
final String label;
|
||||
|
||||
/// Optional param
|
||||
final String? param;
|
||||
|
||||
/// Optional extra object
|
||||
final Object? extra;
|
||||
|
||||
/// Wrap in scaffold
|
||||
final bool withScaffold;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => DetailsPlaceholderScreenState();
|
||||
}
|
||||
|
||||
/// The state for DetailsScreen
|
||||
class DetailsPlaceholderScreenState extends State<DetailsPlaceholderScreen> {
|
||||
int _counter = 0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.withScaffold) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Details Screen - ${widget.label}'),
|
||||
),
|
||||
body: _build(context),
|
||||
);
|
||||
} else {
|
||||
return Container(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: _build(context),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _build(BuildContext context) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'Details for ${widget.label} - Counter: $_counter',
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
const Padding(padding: EdgeInsets.all(4)),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_counter++;
|
||||
});
|
||||
},
|
||||
child: const Text('Increment counter'),
|
||||
),
|
||||
const Padding(padding: EdgeInsets.all(8)),
|
||||
if (widget.param != null)
|
||||
Text(
|
||||
'Parameter: ${widget.param!}',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const Padding(padding: EdgeInsets.all(8)),
|
||||
if (widget.extra != null)
|
||||
Text(
|
||||
'Extra: ${widget.extra!}',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
if (!widget.withScaffold) ...<Widget>[
|
||||
const Padding(padding: EdgeInsets.all(16)),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
GoRouter.of(context).pop();
|
||||
},
|
||||
child: const Text(
|
||||
'< Back',
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+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(
|
||||
|
||||
@@ -164,23 +164,23 @@ class _DeletedFilesListView extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: ListView.builder(
|
||||
itemBuilder: (context, index) {
|
||||
final object = state.objects[index];
|
||||
final deletedFile = state.objects[index];
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
child: ListTile(
|
||||
// TODO(Yijing): implement file type after TrashPB has file type
|
||||
// TODO: show different file type icon, implement this feature after TrashPB has file type field
|
||||
leading: FlowySvg(
|
||||
FlowySvgs.document_s,
|
||||
size: const Size.square(24),
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
title: Text(
|
||||
object.name,
|
||||
deletedFile.name,
|
||||
style: theme.textTheme.labelMedium
|
||||
?.copyWith(color: theme.colorScheme.onBackground),
|
||||
),
|
||||
horizontalTitleGap: 0,
|
||||
// TODO(yiing): needs improve by container/surface theme color
|
||||
tileColor: theme.colorScheme.onSurface.withOpacity(0.1),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
@@ -188,7 +188,6 @@ class _DeletedFilesListView extends StatelessWidget {
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// TODO(yijing): extract icon button
|
||||
IconButton(
|
||||
splashRadius: 20,
|
||||
icon: FlowySvg(
|
||||
@@ -199,10 +198,10 @@ class _DeletedFilesListView extends StatelessWidget {
|
||||
onPressed: () {
|
||||
context
|
||||
.read<TrashBloc>()
|
||||
.add(TrashEvent.putback(object.id));
|
||||
.add(TrashEvent.putback(deletedFile.id));
|
||||
Fluttertoast.showToast(
|
||||
msg:
|
||||
'${object.name} ${LocaleKeys.trash_mobile_isRestored.tr()}',
|
||||
'${deletedFile.name} ${LocaleKeys.trash_mobile_isRestored.tr()}',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
},
|
||||
@@ -215,10 +214,12 @@ class _DeletedFilesListView extends StatelessWidget {
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
onPressed: () {
|
||||
context.read<TrashBloc>().add(TrashEvent.delete(object));
|
||||
context
|
||||
.read<TrashBloc>()
|
||||
.add(TrashEvent.delete(deletedFile));
|
||||
Fluttertoast.showToast(
|
||||
msg:
|
||||
'${object.name} ${LocaleKeys.trash_mobile_isDeleted.tr()}',
|
||||
'${deletedFile.name} ${LocaleKeys.trash_mobile_isDeleted.tr()}',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export 'details_placeholder_page.dart';
|
||||
export 'editor/mobile_editor_screen.dart';
|
||||
export 'home/home.dart';
|
||||
export 'mobile_bottom_navigation_bar.dart';
|
||||
|
||||
+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);
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ class NotificationsSettingGroup extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _NotificationsSettingGroupState extends State<NotificationsSettingGroup> {
|
||||
// TODO(yijing):remove this after notification page is implemented
|
||||
// TODO:remove this after notification page is implemented
|
||||
bool isPushNotificationOn = false;
|
||||
|
||||
@override
|
||||
|
||||
+1
@@ -23,6 +23,7 @@ class _EditUsernameBottomSheetState extends State<EditUsernameBottomSheet> {
|
||||
late TextEditingController _textFieldController;
|
||||
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
+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) {
|
||||
|
||||
+38
-14
@@ -8,28 +8,52 @@ class MobileQuickActionButton extends StatelessWidget {
|
||||
required this.onTap,
|
||||
required this.icon,
|
||||
required this.text,
|
||||
this.color,
|
||||
this.textColor,
|
||||
this.iconColor,
|
||||
this.enable = true,
|
||||
});
|
||||
|
||||
final VoidCallback onTap;
|
||||
final FlowySvgData icon;
|
||||
final String text;
|
||||
final Color? color;
|
||||
final Color? textColor;
|
||||
final Color? iconColor;
|
||||
final bool enable;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Container(
|
||||
height: 44,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
FlowySvg(icon, size: const Size.square(20), color: color),
|
||||
const HSpace(8),
|
||||
FlowyText(text, fontSize: 15, color: color),
|
||||
],
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
if (enable) {
|
||||
onTap();
|
||||
}
|
||||
},
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
overlayColor:
|
||||
enable ? null : const MaterialStatePropertyAll(Colors.transparent),
|
||||
splashColor: Colors.transparent,
|
||||
child: Container(
|
||||
height: 44,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
FlowySvg(
|
||||
icon,
|
||||
size: const Size.square(20),
|
||||
color: enable ? iconColor : Theme.of(context).disabledColor,
|
||||
),
|
||||
const HSpace(12),
|
||||
Expanded(
|
||||
child: FlowyText(
|
||||
text,
|
||||
fontSize: 15,
|
||||
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,
|
||||
});
|
||||
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
extension CalcTypeLabel on CalculationType {
|
||||
String get label => switch (this) {
|
||||
CalculationType.Average =>
|
||||
LocaleKeys.grid_calculationTypeLabel_average.tr(),
|
||||
CalculationType.Max => LocaleKeys.grid_calculationTypeLabel_max.tr(),
|
||||
CalculationType.Median =>
|
||||
LocaleKeys.grid_calculationTypeLabel_median.tr(),
|
||||
CalculationType.Min => LocaleKeys.grid_calculationTypeLabel_min.tr(),
|
||||
CalculationType.Sum => LocaleKeys.grid_calculationTypeLabel_sum.tr(),
|
||||
_ => throw UnimplementedError(
|
||||
'Label for $this has not been implemented',
|
||||
),
|
||||
};
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
import 'dart:async';
|
||||
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:appflowy_result/appflowy_result.dart';
|
||||
import 'package:flowy_infra/notifier.dart';
|
||||
|
||||
typedef UpdateCalculationValue
|
||||
= FlowyResult<CalculationChangesetNotificationPB, FlowyError>;
|
||||
|
||||
class CalculationsListener {
|
||||
CalculationsListener({required this.viewId});
|
||||
|
||||
final String viewId;
|
||||
|
||||
PublishNotifier<UpdateCalculationValue>? _calculationNotifier =
|
||||
PublishNotifier();
|
||||
DatabaseNotificationListener? _listener;
|
||||
|
||||
void start({
|
||||
required void Function(UpdateCalculationValue) onCalculationChanged,
|
||||
}) {
|
||||
_calculationNotifier?.addPublishListener(onCalculationChanged);
|
||||
_listener = DatabaseNotificationListener(
|
||||
objectId: viewId,
|
||||
handler: _handler,
|
||||
);
|
||||
}
|
||||
|
||||
void _handler(
|
||||
DatabaseNotification ty,
|
||||
FlowyResult<Uint8List, FlowyError> result,
|
||||
) {
|
||||
switch (ty) {
|
||||
case DatabaseNotification.DidUpdateCalculation:
|
||||
_calculationNotifier?.value = result.fold(
|
||||
(payload) => FlowyResult.success(
|
||||
CalculationChangesetNotificationPB.fromBuffer(payload),
|
||||
),
|
||||
(err) => FlowyResult.failure(err),
|
||||
);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> stop() async {
|
||||
await _listener?.stop();
|
||||
_calculationNotifier?.dispose();
|
||||
_calculationNotifier = null;
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
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:appflowy_result/appflowy_result.dart';
|
||||
|
||||
class CalculationsBackendService {
|
||||
const CalculationsBackendService({required this.viewId});
|
||||
|
||||
final String viewId;
|
||||
|
||||
// Get Calculations (initial fetch)
|
||||
|
||||
Future<FlowyResult<RepeatedCalculationsPB, FlowyError>>
|
||||
getCalculations() async {
|
||||
final payload = DatabaseViewIdPB()..value = viewId;
|
||||
|
||||
return DatabaseEventGetAllCalculations(payload).send();
|
||||
}
|
||||
|
||||
Future<void> updateCalculation(
|
||||
String fieldId,
|
||||
CalculationType type, {
|
||||
String? calculationId,
|
||||
}) async {
|
||||
final payload = UpdateCalculationChangesetPB()
|
||||
..viewId = viewId
|
||||
..fieldId = fieldId
|
||||
..calculationType = type;
|
||||
|
||||
if (calculationId != null) {
|
||||
payload.calculationId = calculationId;
|
||||
}
|
||||
|
||||
await DatabaseEventUpdateCalculation(payload).send();
|
||||
}
|
||||
|
||||
Future<void> removeCalculation(
|
||||
String fieldId,
|
||||
String calculationId,
|
||||
) async {
|
||||
final payload = RemoveCalculationChangesetPB()
|
||||
..viewId = viewId
|
||||
..fieldId = fieldId
|
||||
..calculationId = calculationId;
|
||||
|
||||
await DatabaseEventRemoveCalculation(payload).send();
|
||||
}
|
||||
}
|
||||
Loaded 100 of 1163 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user