Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
ce261c9128 | ||
|
|
20391bfec4 | ||
|
|
9d28360887 | ||
|
|
99f9cb2a09 | ||
|
|
247405ff51 | ||
|
|
d27e2179cc | ||
|
|
100cc19747 | ||
|
|
fa66a119ed | ||
|
|
a9787546b4 | ||
|
|
5cbe90c79f | ||
|
|
a143a030a9 | ||
|
|
caede19267 | ||
|
|
d410ffbaa8 | ||
|
|
0483e305e7 | ||
|
|
f6242f1cc7 | ||
|
|
18c7fff34b | ||
|
|
792573f46d | ||
|
|
e9d7d0b7b3 | ||
|
|
0b7ae734a1 | ||
|
|
c0aef8f4fe | ||
|
|
55c97b56a3 | ||
|
|
86a0569d84 | ||
|
|
5b3b0e54d9 | ||
|
|
f1e380c276 | ||
|
|
ea93f0e141 | ||
|
|
c1433ce38a | ||
|
|
831586293e | ||
|
|
5c5b6d02e8 | ||
|
|
8937856d36 | ||
|
|
63c22feb8f | ||
|
|
05a06980b9 | ||
|
|
7be29c04a2 | ||
|
|
5a9a1e60e6 | ||
|
|
4811e65efa | ||
|
|
72a23bfe82 | ||
|
|
ed78cb4efc | ||
|
|
d2cc799861 | ||
|
|
2e7a97c8a2 | ||
|
|
64cd7d41ca | ||
|
|
0e3ffa8fd5 | ||
|
|
be00980fdd | ||
|
|
7fbaf204ef | ||
|
|
acc03b8cc4 | ||
|
|
747abba87f | ||
|
|
e88fb533c8 | ||
|
|
3f896ad64f | ||
|
|
6591d5de52 | ||
|
|
a1abcd7626 | ||
|
|
18a355601a | ||
|
|
c2fdfb6e89 | ||
|
|
baa7c8d826 | ||
|
|
8105da1c2b | ||
|
|
827e41348b | ||
|
|
e239ba46aa | ||
|
|
2554ba81b5 | ||
|
|
84d171b1db | ||
|
|
38902ed599 | ||
|
|
2cdee386c2 | ||
|
|
90516b6adc | ||
|
|
c4acba8216 | ||
|
|
4404e0d727 | ||
|
|
48c6c635f7 | ||
|
|
332f40c915 | ||
|
|
2b32f0eff0 | ||
|
|
7d20ca5a77 | ||
|
|
0a0f2adf76 | ||
|
|
4852e5c8d4 | ||
|
|
65f1db776b | ||
|
|
38c3e700e9 | ||
|
|
15cb1b5f19 | ||
|
|
1bf1d03676 | ||
|
|
f612d9b701 |
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,29 @@
|
||||
name: Build Bot
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
dispatch_slash_command:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# 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"
|
||||
@@ -248,8 +248,9 @@ jobs:
|
||||
working-directory: AppFlowy-Cloud
|
||||
run: |
|
||||
docker compose down -v --remove-orphans
|
||||
docker pull appflowyinc/appflowy_cloud:latest
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
sleep 10
|
||||
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
@@ -298,6 +299,7 @@ jobs:
|
||||
export DISPLAY=:99
|
||||
sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 &
|
||||
sudo apt-get install network-manager
|
||||
docker ps -a
|
||||
flutter test integration_test/cloud/cloud_runner.dart -d Linux --coverage
|
||||
shell: bash
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ on:
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.18.0-0.2.pre"
|
||||
RUST_TOOLCHAIN: "1.75"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
@@ -46,19 +45,14 @@ 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: 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
|
||||
@@ -70,8 +64,8 @@ jobs:
|
||||
- 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
|
||||
with:
|
||||
|
||||
@@ -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
|
||||
@@ -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,6 +440,17 @@ 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:
|
||||
|
||||
@@ -23,7 +23,6 @@ env:
|
||||
|
||||
jobs:
|
||||
test-on-ubuntu:
|
||||
# environment: SUPABASE_CI
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
@@ -50,22 +49,6 @@ jobs:
|
||||
workspaces: |
|
||||
frontend/rust-lib
|
||||
|
||||
# - name: Create .env file in flowy-server
|
||||
# working-directory: frontend/rust-lib/flowy-server
|
||||
# run: |
|
||||
# touch .env.ci
|
||||
# echo SUPABASE_URL=${{ secrets.SUPABASE_URL }} >> .env.ci
|
||||
# echo SUPABASE_ANON_KEY=${{ secrets.SUPABASE_ANON_KEY }} >> .env.ci
|
||||
# echo SUPABASE_JWT_SECRET=${{ secrets.SUPABASE_JWT_SECRET }} >> .env.ci
|
||||
#
|
||||
# - name: Create .env file in event-integration
|
||||
# working-directory: frontend/rust-lib/event-integration
|
||||
# run: |
|
||||
# touch .env.ci
|
||||
# echo SUPABASE_URL=${{ secrets.SUPABASE_URL }} >> .env.ci
|
||||
# echo SUPABASE_ANON_KEY=${{ secrets.SUPABASE_ANON_KEY }} >> .env.ci
|
||||
# echo SUPABASE_JWT_SECRET=${{ secrets.SUPABASE_JWT_SECRET }} >> .env.ci
|
||||
|
||||
- name: Checkout appflowy cloud code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
@@ -95,7 +78,7 @@ jobs:
|
||||
af_cloud_test_base_url: http://localhost
|
||||
af_cloud_test_ws_url: ws://localhost/ws
|
||||
af_cloud_test_gotrue_url: http://localhost/gotrue
|
||||
run: cargo test --no-default-features --features="rev-sqlite" -- --nocapture
|
||||
run: cargo test --no-default-features --features="rev-sqlite,dart" -- --nocapture
|
||||
|
||||
|
||||
- name: rustfmt rust-lib
|
||||
@@ -104,4 +87,4 @@ jobs:
|
||||
|
||||
- name: clippy rust-lib
|
||||
run: cargo clippy --all-targets -- -D warnings
|
||||
working-directory: frontend/rust-lib
|
||||
working-directory: frontend/rust-lib
|
||||
@@ -79,4 +79,4 @@ jobs:
|
||||
working-directory: frontend/appflowy_web
|
||||
run: |
|
||||
pnpm install
|
||||
pnpm run wasm
|
||||
pnpm run build_release_wasm
|
||||
@@ -1,4 +1,51 @@
|
||||
# Release Notes
|
||||
## 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.
|
||||
- Enabled anonymous sign-in option for mobile platform users.
|
||||
- Introduced the ability to customize cloud settings directly from the startup page.
|
||||
- Added support for inserting reminders on the mobile platform.
|
||||
- Overhauled the user interface on mobile devices, including improvements to the action bottom sheet, editor toolbar, database details page, and app bar.
|
||||
- Implemented a shortcut (F2 key) to rename the current view.
|
||||
|
||||
### Bug Fixes
|
||||
- Fixed an issue where the font family was not displaying correctly on the mobile platform.
|
||||
- Resolved a problem with the mobile row detail title not updating correctly.
|
||||
- Fixed issues related to deleting images and refactored the image actions menu for better usability.
|
||||
- Fixed other known issues.
|
||||
|
||||
# Release Notes
|
||||
## Version 0.4.3 - 01/16/2024
|
||||
### Bug Fixes
|
||||
- Fixed file name too long issue
|
||||
|
||||
## Version 0.4.2 - 01/15/2024
|
||||
AppFlowy for Android is available to download on GitHub.
|
||||
If you’ve been using our desktop app, it’s important to read [this guide](https://docs.appflowy.io/docs/guides/sync-desktop-and-mobile) before logging into the mobile app.
|
||||
### New Features
|
||||
- Enhanced RTL (Right-to-Left) support for mobile platforms.
|
||||
- Optimized selection gesture system on mobile.
|
||||
- Optimized the mobile toolbar menu.
|
||||
- Improved reference menu (‘@’ menu).
|
||||
- Updated privacy policy.
|
||||
- Improved the data import process for AppFlowy by implementing a progress indicator and compressing the data to enhance efficiency.
|
||||
- Enhanced the utilization of local disk space to optimize storage consumption.
|
||||
### Bug Fixes
|
||||
- Fixed sign-in cancellation issue on mobile.
|
||||
- Resolved keyboard close bug on Android.
|
||||
|
||||
|
||||
## Version 0.4.1 - 01/03/2024
|
||||
### Bug fixes
|
||||
|
||||
@@ -10,6 +10,7 @@ extend = [
|
||||
{ path = "scripts/makefile/flutter.toml" },
|
||||
{ path = "scripts/makefile/tool.toml" },
|
||||
{ path = "scripts/makefile/tauri.toml" },
|
||||
{ path = "scripts/makefile/web.toml" },
|
||||
]
|
||||
|
||||
[config]
|
||||
@@ -25,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.2"
|
||||
APPFLOWY_VERSION = "0.4.8"
|
||||
FLUTTER_DESKTOP_FEATURES = "dart,rev-sqlite"
|
||||
PRODUCT_NAME = "AppFlowy"
|
||||
MACOSX_DEPLOYMENT_TARGET = "11.0"
|
||||
@@ -48,6 +49,8 @@ LIB_EXT = "a"
|
||||
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"
|
||||
|
||||
@@ -30,16 +30,26 @@ linter:
|
||||
rules:
|
||||
- require_trailing_commas
|
||||
|
||||
- prefer_collection_literals
|
||||
- prefer_final_fields
|
||||
- prefer_final_in_for_each
|
||||
- prefer_final_locals
|
||||
|
||||
- sized_box_for_whitespace
|
||||
- use_decorated_box
|
||||
|
||||
- unnecessary_parenthesis
|
||||
- unnecessary_await_in_return
|
||||
- unnecessary_raw_strings
|
||||
|
||||
- avoid_unnecessary_containers
|
||||
- avoid_redundant_argument_values
|
||||
- avoid_unused_constructor_parameters
|
||||
|
||||
- always_declare_return_types
|
||||
- unnecessary_raw_strings
|
||||
- use_decorated_box
|
||||
|
||||
- sort_constructors_first
|
||||
- unawaited_futures
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
|
||||
@@ -52,7 +52,7 @@ android {
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "io.appflowy.appflowy"
|
||||
minSdkVersion 33
|
||||
minSdkVersion 29
|
||||
targetSdkVersion 33
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
|
||||
@@ -73,9 +73,9 @@
|
||||
"attributes": { "subtype": "checkbox", "checkbox": true },
|
||||
"delta": [
|
||||
{ "insert": "Click " },
|
||||
{ "insert": "+ New Page ", "attributes": { "code": true } },
|
||||
{ "insert": "+ New Page", "attributes": { "code": true } },
|
||||
{
|
||||
"insert": "button at the bottom of your sidebar to add a new page."
|
||||
"insert": " button at the bottom of your sidebar to add a new page."
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -3,7 +3,6 @@ import 'package:appflowy/plugins/database/board/presentation/widgets/board_colum
|
||||
import 'package:appflowy/plugins/database/widgets/card/container/card_container.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_board/appflowy_board.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
@@ -25,11 +24,11 @@ void main() {
|
||||
|
||||
final findFirstCard = find.descendant(
|
||||
of: find.byType(AppFlowyGroupCard),
|
||||
matching: find.byType(FlowyText),
|
||||
matching: find.byType(Text),
|
||||
);
|
||||
|
||||
FlowyText firstCardText = tester.firstWidget(findFirstCard);
|
||||
expect(firstCardText.text, defaultFirstCardName);
|
||||
Text firstCardText = tester.firstWidget(findFirstCard);
|
||||
expect(firstCardText.data, defaultFirstCardName);
|
||||
|
||||
await tester.tap(
|
||||
find
|
||||
@@ -51,13 +50,13 @@ void main() {
|
||||
),
|
||||
newCardName,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||
|
||||
await tester.tap(find.byType(AppFlowyBoard));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
firstCardText = tester.firstWidget(findFirstCard);
|
||||
expect(firstCardText.text, newCardName);
|
||||
expect(firstCardText.data, newCardName);
|
||||
});
|
||||
|
||||
testWidgets('from footer', (tester) async {
|
||||
@@ -68,12 +67,11 @@ void main() {
|
||||
|
||||
final findLastCard = find.descendant(
|
||||
of: find.byType(AppFlowyGroupCard),
|
||||
matching: find.byType(FlowyText),
|
||||
matching: find.byType(Text),
|
||||
);
|
||||
|
||||
FlowyText? lastCardText =
|
||||
tester.widgetList(findLastCard).last as FlowyText;
|
||||
expect(lastCardText.text, defaultLastCardName);
|
||||
Text? lastCardText = tester.widgetList(findLastCard).last as Text;
|
||||
expect(lastCardText.data, defaultLastCardName);
|
||||
|
||||
await tester.tap(
|
||||
find
|
||||
@@ -93,13 +91,13 @@ void main() {
|
||||
),
|
||||
newCardName,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||
|
||||
await tester.tap(find.byType(AppFlowyBoard));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
lastCardText = tester.widgetList(findLastCard).last as FlowyText;
|
||||
expect(lastCardText.text, newCardName);
|
||||
lastCardText = tester.widgetList(findLastCard).last as Text;
|
||||
expect(lastCardText.data, newCardName);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/select_option_cell/extension.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell_editor/extension.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/row_property.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:appflowy_board/appflowy_board.dart';
|
||||
@@ -19,10 +19,10 @@ void main() {
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Board);
|
||||
final card1 = find.ancestor(
|
||||
of: find.findTextInFlowyText(card1Name),
|
||||
of: find.text(card1Name),
|
||||
matching: find.byType(AppFlowyGroupCard),
|
||||
);
|
||||
final doingGroup = find.findTextInFlowyText('Doing');
|
||||
final doingGroup = find.text('Doing');
|
||||
final doingGroupCenter = tester.getCenter(doingGroup);
|
||||
final card1Center = tester.getCenter(card1);
|
||||
|
||||
@@ -39,11 +39,11 @@ void main() {
|
||||
of: find.byType(RowPropertyList),
|
||||
matching: find.descendant(
|
||||
of: find.byType(SelectOptionTag),
|
||||
matching: find.byType(FlowyText),
|
||||
matching: find.byType(Text),
|
||||
),
|
||||
);
|
||||
expect(card1StatusFinder, findsNWidgets(1));
|
||||
final card1StatusText = tester.widget<FlowyText>(card1StatusFinder).text;
|
||||
final card1StatusText = tester.widget<Text>(card1StatusFinder).data;
|
||||
expect(card1StatusText, 'Doing');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,16 +20,16 @@ void main() {
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Board);
|
||||
const name = 'Card 1';
|
||||
final card1 = find.findTextInFlowyText(name);
|
||||
final card1 = find.text(name);
|
||||
await tester.hoverOnWidget(
|
||||
card1,
|
||||
onHover: () async {
|
||||
final moreOption = find.byType(CardMoreOption);
|
||||
final moreOption = find.byType(MoreCardOptionsAccessory);
|
||||
await tester.tapButton(moreOption);
|
||||
},
|
||||
);
|
||||
await tester.tapButtonWithName(LocaleKeys.button_delete.tr());
|
||||
expect(find.findTextInFlowyText(name), findsNothing);
|
||||
expect(find.text(name), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('duplicate item in ToDo card', (tester) async {
|
||||
@@ -38,11 +38,11 @@ void main() {
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Board);
|
||||
const name = 'Card 1';
|
||||
final card1 = find.findTextInFlowyText(name);
|
||||
final card1 = find.text(name);
|
||||
await tester.hoverOnWidget(
|
||||
card1,
|
||||
onHover: () async {
|
||||
final moreOption = find.byType(CardMoreOption);
|
||||
final moreOption = find.byType(MoreCardOptionsAccessory);
|
||||
await tester.tapButton(moreOption);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -32,8 +32,8 @@ void main() {
|
||||
const service = TestWorkspaceService(TestWorkspace.board);
|
||||
|
||||
group('board', () {
|
||||
setUpAll(() async => await service.setUpAll());
|
||||
setUp(() async => await service.setUp());
|
||||
setUpAll(() async => service.setUpAll());
|
||||
setUp(() async => service.setUp());
|
||||
|
||||
testWidgets('open the board with data structure in v0.2.0', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
|
||||
@@ -38,9 +38,7 @@ void main() {
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
// create a new document called Sample
|
||||
await tester.createNewPage(
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
await tester.createNewPage();
|
||||
|
||||
// focus on the editor
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
|
||||
@@ -80,7 +80,7 @@ void main() {
|
||||
// Hover over today's calendar cell
|
||||
await tester.hoverOnTodayCalendarCell(
|
||||
// Tap on create new event button
|
||||
onHover: () async => await tester.tapAddCalendarEventButton(),
|
||||
onHover: () async => tester.tapAddCalendarEventButton(),
|
||||
);
|
||||
|
||||
// Make sure that the event editor popup is shown
|
||||
@@ -158,7 +158,7 @@ void main() {
|
||||
|
||||
// Create a new event on the first of this month
|
||||
final today = DateTime.now();
|
||||
final firstOfThisMonth = DateTime(today.year, today.month, 1);
|
||||
final firstOfThisMonth = DateTime(today.year, today.month);
|
||||
await tester.doubleClickCalendarCell(firstOfThisMonth);
|
||||
await tester.dismissEventEditor();
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ void main() {
|
||||
input: 'hello world',
|
||||
);
|
||||
|
||||
await tester.assertCellContent(
|
||||
tester.assertCellContent(
|
||||
rowIndex: 0,
|
||||
fieldType: FieldType.RichText,
|
||||
content: 'hello world',
|
||||
@@ -56,14 +56,13 @@ void main() {
|
||||
cellIndex: 1,
|
||||
);
|
||||
|
||||
await tester.assertCellContent(
|
||||
tester.assertCellContent(
|
||||
rowIndex: 0,
|
||||
fieldType: FieldType.RichText,
|
||||
content: 'hello',
|
||||
cellIndex: 0,
|
||||
);
|
||||
|
||||
await tester.assertCellContent(
|
||||
tester.assertCellContent(
|
||||
rowIndex: 0,
|
||||
fieldType: FieldType.RichText,
|
||||
content: 'world',
|
||||
@@ -96,7 +95,7 @@ void main() {
|
||||
input: '0.2',
|
||||
);
|
||||
// -1 -> -1
|
||||
await tester.assertCellContent(
|
||||
tester.assertCellContent(
|
||||
rowIndex: 0,
|
||||
fieldType: fieldType,
|
||||
content: '-1',
|
||||
@@ -109,7 +108,7 @@ void main() {
|
||||
input: '.1',
|
||||
);
|
||||
// 0.2 -> 0.2
|
||||
await tester.assertCellContent(
|
||||
tester.assertCellContent(
|
||||
rowIndex: 1,
|
||||
fieldType: fieldType,
|
||||
content: '0.2',
|
||||
@@ -122,7 +121,7 @@ void main() {
|
||||
input: '',
|
||||
);
|
||||
// .1 -> 0.1
|
||||
await tester.assertCellContent(
|
||||
tester.assertCellContent(
|
||||
rowIndex: 2,
|
||||
fieldType: fieldType,
|
||||
content: '0.1',
|
||||
@@ -216,13 +215,13 @@ void main() {
|
||||
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
await tester.assertDateCellInGrid(
|
||||
tester.assertCellContent(
|
||||
rowIndex: 0,
|
||||
fieldType: FieldType.DateTime,
|
||||
content: DateFormat('MMM dd, y').format(today),
|
||||
);
|
||||
|
||||
await tester.tapCellInGrid(rowIndex: 0, fieldType: fieldType);
|
||||
await tester.findDateEditor(findsOneWidget);
|
||||
|
||||
// Toggle include time
|
||||
final now = DateTime.now();
|
||||
@@ -230,8 +229,9 @@ void main() {
|
||||
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
await tester.assertDateCellInGrid(
|
||||
tester.assertCellContent(
|
||||
rowIndex: 0,
|
||||
fieldType: FieldType.DateTime,
|
||||
content: DateFormat('MMM dd, y HH:mm').format(now),
|
||||
);
|
||||
|
||||
@@ -239,12 +239,14 @@ void main() {
|
||||
await tester.findDateEditor(findsOneWidget);
|
||||
|
||||
// Change date format
|
||||
await tester.tapChangeDateTimeFormatButton();
|
||||
await tester.changeDateFormat();
|
||||
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
await tester.assertDateCellInGrid(
|
||||
tester.assertCellContent(
|
||||
rowIndex: 0,
|
||||
fieldType: FieldType.DateTime,
|
||||
content: DateFormat('dd/MM/y HH:mm').format(now),
|
||||
);
|
||||
|
||||
@@ -252,12 +254,14 @@ void main() {
|
||||
await tester.findDateEditor(findsOneWidget);
|
||||
|
||||
// Change time format
|
||||
await tester.tapChangeDateTimeFormatButton();
|
||||
await tester.changeTimeFormat();
|
||||
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
await tester.assertDateCellInGrid(
|
||||
tester.assertCellContent(
|
||||
rowIndex: 0,
|
||||
fieldType: FieldType.DateTime,
|
||||
content: DateFormat('dd/MM/y hh:mm a').format(now),
|
||||
);
|
||||
|
||||
@@ -267,8 +271,9 @@ void main() {
|
||||
// Clear the date and time
|
||||
await tester.clearDate();
|
||||
|
||||
await tester.assertDateCellInGrid(
|
||||
tester.assertCellContent(
|
||||
rowIndex: 0,
|
||||
fieldType: FieldType.DateTime,
|
||||
content: '',
|
||||
);
|
||||
|
||||
|
||||
+5
-5
@@ -27,29 +27,29 @@ void main() {
|
||||
// hide the field
|
||||
await tester.tapGridFieldWithName('New field 1');
|
||||
await tester.tapHidePropertyButton();
|
||||
await tester.noFieldWithName('New field 1');
|
||||
tester.noFieldWithName('New field 1');
|
||||
|
||||
// go back to inline database view, expect field to be shown
|
||||
await tester.tapTabBarLinkedViewByViewName('Untitled');
|
||||
await tester.findFieldWithName('New field 1');
|
||||
tester.findFieldWithName('New field 1');
|
||||
|
||||
// go back to linked database view, expect field to be hidden
|
||||
await tester.tapTabBarLinkedViewByViewName('Grid');
|
||||
await tester.noFieldWithName('New field 1');
|
||||
tester.noFieldWithName('New field 1');
|
||||
|
||||
// use the settings button to show the field
|
||||
await tester.tapDatabaseSettingButton();
|
||||
await tester.tapViewPropertiesButton();
|
||||
await tester.tapViewTogglePropertyVisibilityButtonByName('New field 1');
|
||||
await tester.dismissFieldEditor();
|
||||
await tester.findFieldWithName('New field 1');
|
||||
tester.findFieldWithName('New field 1');
|
||||
|
||||
// open first row in popup then hide the field
|
||||
await tester.openFirstRowDetailPage();
|
||||
await tester.tapGridFieldWithNameInRowDetailPage('New field 1');
|
||||
await tester.tapHidePropertyButtonInFieldEditor();
|
||||
await tester.dismissRowDetailPage();
|
||||
await tester.noFieldWithName('New field 1');
|
||||
tester.noFieldWithName('New field 1');
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/header/type_option/select/select_option.dart';
|
||||
import 'package:appflowy/util/field_type_extension.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import '../util/database_test_op.dart';
|
||||
import '../util/util.dart';
|
||||
@@ -21,7 +23,7 @@ void main() {
|
||||
|
||||
// Invoke the field editor
|
||||
await tester.tapGridFieldWithName('Name');
|
||||
await tester.tapEditPropertyButton();
|
||||
await tester.tapEditFieldButton();
|
||||
|
||||
await tester.renameField('hello world');
|
||||
await tester.dismissFieldEditor();
|
||||
@@ -37,12 +39,7 @@ void main() {
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
|
||||
// Invoke the field editor
|
||||
await tester.tapGridFieldWithName('Type');
|
||||
await tester.tapEditPropertyButton();
|
||||
|
||||
await tester.tapTypeOptionButton();
|
||||
await tester.selectFieldType(FieldType.Checkbox);
|
||||
await tester.dismissFieldEditor();
|
||||
await tester.changeFieldTypeOfFieldWithName('Type', FieldType.Checkbox);
|
||||
|
||||
await tester.assertFieldTypeWithFieldName(
|
||||
'Type',
|
||||
@@ -62,7 +59,7 @@ void main() {
|
||||
await tester.createField(FieldType.Checklist, 'checklist');
|
||||
|
||||
// check the field is created successfully
|
||||
await tester.findFieldWithName('checklist');
|
||||
tester.findFieldWithName('checklist');
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
@@ -82,7 +79,7 @@ void main() {
|
||||
// confirm delete
|
||||
await tester.tapDialogOkButton();
|
||||
|
||||
await tester.noFieldWithName('New field 1');
|
||||
tester.noFieldWithName('New field 1');
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
@@ -102,7 +99,7 @@ void main() {
|
||||
await tester.tapGridFieldWithName('New field 1');
|
||||
await tester.tapDuplicatePropertyButton();
|
||||
|
||||
await tester.findFieldWithName('New field 1 (copy)');
|
||||
tester.findFieldWithName('New field 1 (copy)');
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
@@ -118,13 +115,13 @@ void main() {
|
||||
await tester.tapGridFieldWithName('Type');
|
||||
await tester.tapInsertFieldButton(left: false, name: 'Right');
|
||||
await tester.dismissFieldEditor();
|
||||
await tester.findFieldWithName('Right');
|
||||
tester.findFieldWithName('Right');
|
||||
|
||||
// insert new field to the right
|
||||
await tester.tapGridFieldWithName('Type');
|
||||
await tester.tapInsertFieldButton(left: true, name: "Left");
|
||||
await tester.dismissFieldEditor();
|
||||
await tester.findFieldWithName('Left');
|
||||
tester.findFieldWithName('Left');
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
@@ -139,7 +136,7 @@ void main() {
|
||||
await tester.tapNewPropertyButton();
|
||||
|
||||
// Open the type option menu
|
||||
await tester.tapTypeOptionButton();
|
||||
await tester.tapSwitchFieldTypeButton();
|
||||
|
||||
await tester.selectFieldType(FieldType.Checklist);
|
||||
|
||||
@@ -170,7 +167,7 @@ void main() {
|
||||
await tester.renameField(fieldType.name);
|
||||
|
||||
// Open the type option menu
|
||||
await tester.tapTypeOptionButton();
|
||||
await tester.tapSwitchFieldTypeButton();
|
||||
|
||||
await tester.selectFieldType(fieldType);
|
||||
await tester.dismissFieldEditor();
|
||||
@@ -181,6 +178,84 @@ void main() {
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('field types with empty type option editor', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
|
||||
for (final fieldType in [
|
||||
FieldType.RichText,
|
||||
FieldType.Checkbox,
|
||||
FieldType.Checklist,
|
||||
FieldType.URL,
|
||||
]) {
|
||||
// create the field
|
||||
await tester.scrollToRight(find.byType(GridPage));
|
||||
await tester.tapNewPropertyButton();
|
||||
await tester.renameField(fieldType.i18n);
|
||||
|
||||
// change field type
|
||||
await tester.tapSwitchFieldTypeButton();
|
||||
await tester.selectFieldType(fieldType);
|
||||
await tester.dismissFieldEditor();
|
||||
|
||||
// open the field editor
|
||||
await tester.tapGridFieldWithName(fieldType.i18n);
|
||||
await tester.tapEditFieldButton();
|
||||
|
||||
// check type option editor is empty
|
||||
tester.expectEmptyTypeOptionEditor();
|
||||
await tester.dismissFieldEditor();
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('number field type option', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
await tester.scrollToRight(find.byType(GridPage));
|
||||
|
||||
// create a number field
|
||||
await tester.tapNewPropertyButton();
|
||||
await tester.renameField("Number");
|
||||
await tester.tapSwitchFieldTypeButton();
|
||||
await tester.selectFieldType(FieldType.Number);
|
||||
await tester.dismissFieldEditor();
|
||||
|
||||
// enter some data into the first number cell
|
||||
await tester.editCell(
|
||||
rowIndex: 0,
|
||||
fieldType: FieldType.Number,
|
||||
input: '123',
|
||||
);
|
||||
// edit the next cell to force the previous cell at row 0 to lose focus
|
||||
await tester.editCell(
|
||||
rowIndex: 1,
|
||||
fieldType: FieldType.Number,
|
||||
input: '0.2',
|
||||
);
|
||||
tester.assertCellContent(
|
||||
rowIndex: 0,
|
||||
fieldType: FieldType.Number,
|
||||
content: '123',
|
||||
);
|
||||
|
||||
// open editor and change number format
|
||||
await tester.tapGridFieldWithName('Number');
|
||||
await tester.tapEditFieldButton();
|
||||
await tester.changeNumberFieldFormat();
|
||||
await tester.dismissFieldEditor();
|
||||
|
||||
// assert number format has been changed
|
||||
tester.assertCellContent(
|
||||
rowIndex: 0,
|
||||
fieldType: FieldType.Number,
|
||||
content: '\$123',
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('add option', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
@@ -189,9 +264,9 @@ void main() {
|
||||
layout: ViewLayoutPB.Grid,
|
||||
);
|
||||
|
||||
// Invoke the field editor
|
||||
// invoke the field editor
|
||||
await tester.tapGridFieldWithName('Type');
|
||||
await tester.tapEditPropertyButton();
|
||||
await tester.tapEditFieldButton();
|
||||
|
||||
// tap 'add option' button
|
||||
await tester.tapAddSelectOptionButton();
|
||||
@@ -203,10 +278,115 @@ void main() {
|
||||
await tester.enterText(inputField, text);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||
|
||||
// check the result
|
||||
tester.expectToSeeText(text);
|
||||
});
|
||||
|
||||
testWidgets('date time field type options', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
await tester.scrollToRight(find.byType(GridPage));
|
||||
|
||||
// create a date field
|
||||
await tester.tapNewPropertyButton();
|
||||
await tester.renameField(FieldType.DateTime.i18n);
|
||||
await tester.tapSwitchFieldTypeButton();
|
||||
await tester.selectFieldType(FieldType.DateTime);
|
||||
await tester.dismissFieldEditor();
|
||||
|
||||
// edit the first date cell
|
||||
await tester.tapCellInGrid(rowIndex: 0, fieldType: FieldType.DateTime);
|
||||
await tester.toggleIncludeTime();
|
||||
final now = DateTime.now();
|
||||
await tester.selectDay(content: now.day);
|
||||
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
tester.assertCellContent(
|
||||
rowIndex: 0,
|
||||
fieldType: FieldType.DateTime,
|
||||
content: DateFormat('MMM dd, y HH:mm').format(now),
|
||||
);
|
||||
|
||||
// open editor and change date & time format
|
||||
await tester.tapGridFieldWithName(FieldType.DateTime.i18n);
|
||||
await tester.tapEditFieldButton();
|
||||
await tester.changeDateFormat();
|
||||
await tester.changeTimeFormat();
|
||||
await tester.dismissFieldEditor();
|
||||
|
||||
// assert date format has been changed
|
||||
tester.assertCellContent(
|
||||
rowIndex: 0,
|
||||
fieldType: FieldType.DateTime,
|
||||
content: DateFormat('dd/MM/y hh:mm a').format(now),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('last modified and created at field type options',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
final created = DateTime.now();
|
||||
|
||||
// create a created at field
|
||||
await tester.tapNewPropertyButton();
|
||||
await tester.renameField(FieldType.CreatedTime.i18n);
|
||||
await tester.tapSwitchFieldTypeButton();
|
||||
await tester.selectFieldType(FieldType.CreatedTime);
|
||||
await tester.dismissFieldEditor();
|
||||
|
||||
// create a last modified field
|
||||
await tester.tapNewPropertyButton();
|
||||
await tester.renameField(FieldType.LastEditedTime.i18n);
|
||||
await tester.tapSwitchFieldTypeButton();
|
||||
await tester.selectFieldType(FieldType.LastEditedTime);
|
||||
await tester.dismissFieldEditor();
|
||||
|
||||
final modified = DateTime.now();
|
||||
|
||||
tester.assertCellContent(
|
||||
rowIndex: 0,
|
||||
fieldType: FieldType.CreatedTime,
|
||||
content: DateFormat('MMM dd, y HH:mm').format(created),
|
||||
);
|
||||
tester.assertCellContent(
|
||||
rowIndex: 0,
|
||||
fieldType: FieldType.LastEditedTime,
|
||||
content: DateFormat('MMM dd, y HH:mm').format(modified),
|
||||
);
|
||||
|
||||
// open field editor and change date & time format
|
||||
await tester.tapGridFieldWithName(FieldType.LastEditedTime.i18n);
|
||||
await tester.tapEditFieldButton();
|
||||
await tester.changeDateFormat();
|
||||
await tester.changeTimeFormat();
|
||||
await tester.dismissFieldEditor();
|
||||
|
||||
// open field editor and change date & time format
|
||||
await tester.tapGridFieldWithName(FieldType.CreatedTime.i18n);
|
||||
await tester.tapEditFieldButton();
|
||||
await tester.changeDateFormat();
|
||||
await tester.changeTimeFormat();
|
||||
await tester.dismissFieldEditor();
|
||||
|
||||
// assert format has been changed
|
||||
tester.assertCellContent(
|
||||
rowIndex: 0,
|
||||
fieldType: FieldType.CreatedTime,
|
||||
content: DateFormat('dd/MM/y hh:mm a').format(created),
|
||||
);
|
||||
tester.assertCellContent(
|
||||
rowIndex: 0,
|
||||
fieldType: FieldType.LastEditedTime,
|
||||
content: DateFormat('dd/MM/y hh:mm a').format(modified),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
import 'package:appflowy/workspace/presentation/notifications/widgets/notification_item.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/reminder_selector.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.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('reminder in database', () {
|
||||
testWidgets('add date field and add reminder', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
|
||||
// Invoke the field editor
|
||||
await tester.tapGridFieldWithName('Type');
|
||||
await tester.tapEditFieldButton();
|
||||
|
||||
// Change to date type
|
||||
await tester.tapSwitchFieldTypeButton();
|
||||
await tester.selectFieldType(FieldType.DateTime);
|
||||
await tester.dismissFieldEditor();
|
||||
|
||||
// Open date picker
|
||||
await tester.tapCellInGrid(rowIndex: 0, fieldType: FieldType.DateTime);
|
||||
await tester.findDateEditor(findsOneWidget);
|
||||
|
||||
// Select date
|
||||
final isToday = await tester.selectLastDateInPicker();
|
||||
|
||||
// Select "On day of event" reminder
|
||||
await tester.selectReminderOption(ReminderOption.onDayOfEvent);
|
||||
|
||||
// Expect "On day of event" to be displayed
|
||||
tester.expectSelectedReminder(ReminderOption.onDayOfEvent);
|
||||
|
||||
// Dismiss the cell/date editor
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
// Open date picker again
|
||||
await tester.tapCellInGrid(rowIndex: 0, fieldType: FieldType.DateTime);
|
||||
await tester.findDateEditor(findsOneWidget);
|
||||
|
||||
// Expect "On day of event" to be displayed
|
||||
tester.expectSelectedReminder(ReminderOption.onDayOfEvent);
|
||||
|
||||
// Dismiss the cell/date editor
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
int tabIndex = 1;
|
||||
final now = DateTime.now();
|
||||
if (isToday && now.hour >= 9) {
|
||||
tabIndex = 0;
|
||||
}
|
||||
|
||||
// Open "Upcoming" in Notification hub
|
||||
await tester.openNotificationHub(tabIndex: tabIndex);
|
||||
|
||||
// Expect 1 notification
|
||||
tester.expectNotificationItems(1);
|
||||
});
|
||||
|
||||
testWidgets('navigate from reminder to open row', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
|
||||
// Invoke the field editor
|
||||
await tester.tapGridFieldWithName('Type');
|
||||
await tester.tapEditFieldButton();
|
||||
|
||||
// Change to date type
|
||||
await tester.tapSwitchFieldTypeButton();
|
||||
await tester.selectFieldType(FieldType.DateTime);
|
||||
await tester.dismissFieldEditor();
|
||||
|
||||
// Open date picker
|
||||
await tester.tapCellInGrid(rowIndex: 0, fieldType: FieldType.DateTime);
|
||||
await tester.findDateEditor(findsOneWidget);
|
||||
|
||||
// Select date
|
||||
final isToday = await tester.selectLastDateInPicker();
|
||||
|
||||
// Select "On day of event"-reminder
|
||||
await tester.selectReminderOption(ReminderOption.onDayOfEvent);
|
||||
|
||||
// Expect "On day of event" to be displayed
|
||||
tester.expectSelectedReminder(ReminderOption.onDayOfEvent);
|
||||
|
||||
// Dismiss the cell/date editor
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
// Open date picker again
|
||||
await tester.tapCellInGrid(rowIndex: 0, fieldType: FieldType.DateTime);
|
||||
await tester.findDateEditor(findsOneWidget);
|
||||
|
||||
// Expect "On day of event" to be displayed
|
||||
tester.expectSelectedReminder(ReminderOption.onDayOfEvent);
|
||||
|
||||
// Dismiss the cell/date editor
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
// Create and Navigate to a new document
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
int tabIndex = 1;
|
||||
final now = DateTime.now();
|
||||
if (isToday && now.hour >= 9) {
|
||||
tabIndex = 0;
|
||||
}
|
||||
|
||||
// Open correct tab in Notification hub
|
||||
await tester.openNotificationHub(tabIndex: tabIndex);
|
||||
|
||||
// Expect 1 notification
|
||||
tester.expectNotificationItems(1);
|
||||
|
||||
// Tap on the notification
|
||||
await tester.tap(find.byType(NotificationItem));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Expect to see Row Editor Dialog
|
||||
tester.expectToSeeRowDetailsPageDialog();
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'toggle include time sets reminder option correctly',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
layout: ViewLayoutPB.Grid,
|
||||
);
|
||||
|
||||
// Invoke the field editor
|
||||
await tester.tapGridFieldWithName('Type');
|
||||
await tester.tapEditFieldButton();
|
||||
|
||||
// Change to date type
|
||||
await tester.tapSwitchFieldTypeButton();
|
||||
await tester.selectFieldType(FieldType.DateTime);
|
||||
await tester.dismissFieldEditor();
|
||||
|
||||
// Open date picker
|
||||
await tester.tapCellInGrid(rowIndex: 0, fieldType: FieldType.DateTime);
|
||||
await tester.findDateEditor(findsOneWidget);
|
||||
|
||||
// Select date
|
||||
await tester.selectLastDateInPicker();
|
||||
|
||||
// Select "On day of event"-reminder
|
||||
await tester.selectReminderOption(ReminderOption.onDayOfEvent);
|
||||
|
||||
// Expect "On day of event" to be displayed
|
||||
tester.expectSelectedReminder(ReminderOption.onDayOfEvent);
|
||||
|
||||
// Dismiss the cell/date editor
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
// Open date picker again
|
||||
await tester.tapCellInGrid(rowIndex: 0, fieldType: FieldType.DateTime);
|
||||
await tester.findDateEditor(findsOneWidget);
|
||||
|
||||
// Expect "On day of event" to be displayed
|
||||
tester.expectSelectedReminder(ReminderOption.onDayOfEvent);
|
||||
|
||||
// Toggle include time on
|
||||
await tester.toggleIncludeTime();
|
||||
|
||||
// Expect "At time of event" to be displayed
|
||||
tester.expectSelectedReminder(ReminderOption.atTimeOfEvent);
|
||||
|
||||
// Dismiss the cell/date editor
|
||||
await tester.dismissCellEditor();
|
||||
|
||||
// Open date picker again
|
||||
await tester.tapCellInGrid(rowIndex: 0, fieldType: FieldType.DateTime);
|
||||
await tester.findDateEditor(findsOneWidget);
|
||||
|
||||
// Expect "At time of event" to be displayed
|
||||
tester.expectSelectedReminder(ReminderOption.atTimeOfEvent);
|
||||
|
||||
// Select "One hour before"-reminder
|
||||
await tester.selectReminderOption(ReminderOption.oneHourBefore);
|
||||
|
||||
// Expect "One hour before" to be displayed
|
||||
tester.expectSelectedReminder(ReminderOption.oneHourBefore);
|
||||
|
||||
// Toggle include time off
|
||||
await tester.toggleIncludeTime();
|
||||
|
||||
// Expect "On day of event" to be displayed
|
||||
tester.expectSelectedReminder(ReminderOption.onDayOfEvent);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -14,8 +14,8 @@ import '../util/util.dart';
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('grid', () {
|
||||
testWidgets('row details page opens', (tester) async {
|
||||
group('grid row detail page:', () {
|
||||
testWidgets('opens', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
@@ -27,9 +27,12 @@ void main() {
|
||||
|
||||
// Make sure that the row page is opened
|
||||
tester.assertRowDetailPageOpened();
|
||||
|
||||
// Each row detail page should have a document
|
||||
await tester.assertDocumentExistInRowDetailPage();
|
||||
});
|
||||
|
||||
testWidgets('insert emoji in the row detail page', (tester) async {
|
||||
testWidgets('add emoji', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
@@ -48,7 +51,7 @@ void main() {
|
||||
await tester.tapButton(find.byType(EmojiButton));
|
||||
});
|
||||
|
||||
testWidgets('update emoji in the row detail page', (tester) async {
|
||||
testWidgets('update emoji', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
@@ -75,7 +78,7 @@ void main() {
|
||||
expect(emojiText, findsNWidgets(2));
|
||||
});
|
||||
|
||||
testWidgets('remove emoji in the row detail page', (tester) async {
|
||||
testWidgets('remove emoji', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
@@ -96,7 +99,7 @@ void main() {
|
||||
expect(emojiText, findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('create list of fields in row detail page', (tester) async {
|
||||
testWidgets('create list of fields', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
@@ -120,7 +123,7 @@ void main() {
|
||||
await tester.renameField(fieldType.name);
|
||||
|
||||
// Open the type option menu
|
||||
await tester.tapTypeOptionButton();
|
||||
await tester.tapSwitchFieldTypeButton();
|
||||
|
||||
await tester.selectFieldType(fieldType);
|
||||
|
||||
@@ -211,20 +214,6 @@ void main() {
|
||||
tester.assertToggleShowHiddenFieldsVisibility(false);
|
||||
});
|
||||
|
||||
testWidgets('check document exists in row detail page', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
// Create a new grid
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
|
||||
// Hover first row and then open the row page
|
||||
await tester.openFirstRowDetailPage();
|
||||
|
||||
// Each row detail page should have a document
|
||||
await tester.assertDocumentExistInRowDetailPage();
|
||||
});
|
||||
|
||||
testWidgets('update the contents of the document and re-open it',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
@@ -305,7 +294,7 @@ void main() {
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
|
||||
testWidgets('delete row in row detail page', (tester) async {
|
||||
testWidgets('delete row', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
@@ -322,7 +311,7 @@ void main() {
|
||||
await tester.assertNumberOfRowsInGridPage(2);
|
||||
});
|
||||
|
||||
testWidgets('duplicate row in row detail page', (tester) async {
|
||||
testWidgets('duplicate row', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ void main() {
|
||||
// check the text cell
|
||||
final textCells = <String>['A', 'B', 'C', 'D', 'E', '', '', '', '', ''];
|
||||
for (final (index, content) in textCells.indexed) {
|
||||
await tester.assertCellContent(
|
||||
tester.assertCellContent(
|
||||
rowIndex: index,
|
||||
fieldType: FieldType.RichText,
|
||||
content: content,
|
||||
@@ -57,7 +57,7 @@ void main() {
|
||||
'',
|
||||
];
|
||||
for (final (index, content) in numberCells.indexed) {
|
||||
await tester.assertCellContent(
|
||||
tester.assertCellContent(
|
||||
rowIndex: index,
|
||||
fieldType: FieldType.Number,
|
||||
content: content,
|
||||
@@ -74,7 +74,7 @@ void main() {
|
||||
'',
|
||||
];
|
||||
for (final (index, content) in urlCells.indexed) {
|
||||
await tester.assertCellContent(
|
||||
tester.assertCellContent(
|
||||
rowIndex: index,
|
||||
fieldType: FieldType.URL,
|
||||
content: content,
|
||||
@@ -135,7 +135,7 @@ void main() {
|
||||
null,
|
||||
];
|
||||
for (final (index, percent) in checklistCells.indexed) {
|
||||
await tester.assertChecklistCellInGrid(
|
||||
tester.assertChecklistCellInGrid(
|
||||
rowIndex: index,
|
||||
percent: percent,
|
||||
);
|
||||
@@ -155,8 +155,9 @@ void main() {
|
||||
'',
|
||||
];
|
||||
for (final (index, content) in dateCells.indexed) {
|
||||
await tester.assertDateCellInGrid(
|
||||
tester.assertCellContent(
|
||||
rowIndex: index,
|
||||
fieldType: FieldType.DateTime,
|
||||
content: content,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ void main() {
|
||||
'',
|
||||
];
|
||||
for (final (index, content) in textCells.indexed) {
|
||||
await tester.assertCellContent(
|
||||
tester.assertCellContent(
|
||||
rowIndex: index,
|
||||
fieldType: FieldType.RichText,
|
||||
content: content,
|
||||
@@ -51,7 +51,7 @@ void main() {
|
||||
'',
|
||||
'',
|
||||
].indexed) {
|
||||
await tester.assertCellContent(
|
||||
tester.assertCellContent(
|
||||
rowIndex: index,
|
||||
fieldType: FieldType.RichText,
|
||||
content: content,
|
||||
@@ -75,7 +75,7 @@ void main() {
|
||||
'',
|
||||
'',
|
||||
].indexed) {
|
||||
await tester.assertCellContent(
|
||||
tester.assertCellContent(
|
||||
rowIndex: index,
|
||||
fieldType: FieldType.RichText,
|
||||
content: content,
|
||||
@@ -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');
|
||||
|
||||
@@ -153,7 +153,7 @@ void main() {
|
||||
'12',
|
||||
'',
|
||||
].indexed) {
|
||||
await tester.assertCellContent(
|
||||
tester.assertCellContent(
|
||||
rowIndex: index,
|
||||
fieldType: FieldType.Number,
|
||||
content: content,
|
||||
@@ -176,7 +176,7 @@ void main() {
|
||||
'-2',
|
||||
'',
|
||||
].indexed) {
|
||||
await tester.assertCellContent(
|
||||
tester.assertCellContent(
|
||||
rowIndex: index,
|
||||
fieldType: FieldType.Number,
|
||||
content: content,
|
||||
@@ -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');
|
||||
|
||||
@@ -255,7 +255,7 @@ void main() {
|
||||
'2',
|
||||
'',
|
||||
].indexed) {
|
||||
await tester.assertCellContent(
|
||||
tester.assertCellContent(
|
||||
rowIndex: index,
|
||||
fieldType: FieldType.Number,
|
||||
content: content,
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -57,7 +57,7 @@ void main() {
|
||||
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
final first = editorState.getNodeAtPath([0])!;
|
||||
|
||||
|
||||
// expect to see text aligned to the right
|
||||
await FlowyTestKeyboard.simulateKeyDownEvent(
|
||||
[
|
||||
|
||||
+2
-2
@@ -35,7 +35,7 @@ void main() {
|
||||
// delete the readme page
|
||||
await tester.hoverOnPageName(
|
||||
gettingStarted,
|
||||
onHover: () async => await tester.tapDeletePageButton(),
|
||||
onHover: () async => tester.tapDeletePageButton(),
|
||||
);
|
||||
|
||||
// the banner should show up and the readme page should be gone
|
||||
@@ -59,7 +59,7 @@ void main() {
|
||||
// delete the readme page
|
||||
await tester.hoverOnPageName(
|
||||
gettingStarted,
|
||||
onHover: () async => await tester.tapDeletePageButton(),
|
||||
onHover: () async => tester.tapDeletePageButton(),
|
||||
);
|
||||
|
||||
// the banner should show up and the readme page should be gone
|
||||
|
||||
-2
@@ -3,7 +3,6 @@ import 'package:flutter/services.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart';
|
||||
import 'package:appflowy/plugins/inline_actions/widgets/inline_actions_handler.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
@@ -89,7 +88,6 @@ Future<String> createDocumentToReference(WidgetTester tester) async {
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: name,
|
||||
layout: ViewLayoutPB.Document,
|
||||
openAfterCreated: false,
|
||||
);
|
||||
|
||||
|
||||
+2
-2
@@ -29,8 +29,8 @@ void main() {
|
||||
// because this icons are defined in the appflowy_editor package, we can't fetch the icons by SVG data. [textDirectionItems]
|
||||
final textDirectionIconNames = [
|
||||
'toolbar/text_direction_auto',
|
||||
'toolbar/text_direction_left',
|
||||
'toolbar/text_direction_right',
|
||||
'toolbar/text_direction_ltr',
|
||||
'toolbar/text_direction_rtl',
|
||||
];
|
||||
// no text direction items in default/LTR mode
|
||||
var button = find.byWidgetPredicate(
|
||||
|
||||
-2
@@ -43,8 +43,6 @@ void main() {
|
||||
await tester.editor.hoverOnCover();
|
||||
await tester.editor.tapOnChangeCover();
|
||||
await tester.editor.addNetworkImageCover(imageUrl);
|
||||
await tester.editor.switchNetworkImageCover(imageUrl);
|
||||
await tester.editor.dismissCoverPicker();
|
||||
tester.expectToSeeDocumentCover(CoverType.file);
|
||||
|
||||
// Remove the cover
|
||||
|
||||
+5
-9
@@ -1,9 +1,9 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/text.dart';
|
||||
import 'package:appflowy/plugins/inline_actions/widgets/inline_actions_handler.dart';
|
||||
import 'package:appflowy/plugins/database/board/presentation/board_page.dart';
|
||||
import 'package:appflowy/plugins/database/calendar/presentation/calendar_page.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/text_cell/text_cell.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
@@ -40,7 +40,7 @@ void main() {
|
||||
Position(path: [1]),
|
||||
),
|
||||
);
|
||||
final gridTextCell = find.byType(GridTextCell).first;
|
||||
final gridTextCell = find.byType(EditableTextCell).first;
|
||||
await tester.tapButton(gridTextCell);
|
||||
|
||||
expect(tester.editor.getCurrentEditorState().selection, isNull);
|
||||
@@ -84,7 +84,7 @@ void main() {
|
||||
|
||||
await createInlineDatabase(tester, ViewLayoutPB.Grid);
|
||||
|
||||
// validate the referenced grid is inserted
|
||||
// validate the inline grid is created
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(AppFlowyEditor),
|
||||
@@ -100,7 +100,7 @@ void main() {
|
||||
|
||||
await createInlineDatabase(tester, ViewLayoutPB.Board);
|
||||
|
||||
// validate the referenced grid is inserted
|
||||
// validate the inline board is created
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(AppFlowyEditor),
|
||||
@@ -116,7 +116,7 @@ void main() {
|
||||
|
||||
await createInlineDatabase(tester, ViewLayoutPB.Calendar);
|
||||
|
||||
// validate the referenced grid is inserted
|
||||
// validate the inline calendar is created
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(AppFlowyEditor),
|
||||
@@ -144,8 +144,6 @@ Future<void> insertReferenceDatabase(
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'insert_a_reference_${layout.name}',
|
||||
layout: ViewLayoutPB.Document,
|
||||
openAfterCreated: true,
|
||||
);
|
||||
// tap the first line of the document
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
@@ -173,8 +171,6 @@ Future<void> createInlineDatabase(
|
||||
final documentName = 'insert_a_inline_${layout.name}';
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: documentName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
openAfterCreated: true,
|
||||
);
|
||||
// tap the first line of the document
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
|
||||
+7
-5
@@ -1,13 +1,17 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/core/config/kv.dart';
|
||||
import 'package:appflowy/core/config/kv_keys.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/custom_image_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/embed_image_url_widget.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/image_placeholder.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/resizeable_image.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/unsplash_image_widget.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/upload_image_menu.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart' hide UploadImageMenu;
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart'
|
||||
hide UploadImageMenu, ResizableImage;
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -33,7 +37,6 @@ void main() {
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: LocaleKeys.document_plugins_image_addAnImage.tr(),
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
// tap the first line of the document
|
||||
@@ -61,6 +64,7 @@ void main() {
|
||||
paths: [imagePath],
|
||||
);
|
||||
|
||||
await getIt<KeyValueStorage>().set(KVKeys.kCloudType, '0');
|
||||
await tester.tapButtonWithName(
|
||||
LocaleKeys.document_imageBlock_upload_placeholder.tr(),
|
||||
);
|
||||
@@ -81,7 +85,6 @@ void main() {
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: LocaleKeys.document_plugins_image_addAnImage.tr(),
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
// tap the first line of the document
|
||||
@@ -135,7 +138,6 @@ void main() {
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: LocaleKeys.document_plugins_image_addAnImage.tr(),
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
// tap the first line of the document
|
||||
|
||||
-3
@@ -1,6 +1,5 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
@@ -22,7 +21,6 @@ void main() {
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'math equation',
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
// tap the first line of the document
|
||||
@@ -68,7 +66,6 @@ void main() {
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'math equation',
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
// tap the first line of the document
|
||||
|
||||
+2
-3
@@ -65,7 +65,7 @@ void main() {
|
||||
const newName = 'RenameToNewPageName';
|
||||
await tester.hoverOnPageName(
|
||||
pageName,
|
||||
onHover: () async => await tester.renamePage(newName),
|
||||
onHover: () async => tester.renamePage(newName),
|
||||
);
|
||||
final finder = find.descendant(
|
||||
of: find.byType(MentionPageBlock),
|
||||
@@ -84,7 +84,7 @@ void main() {
|
||||
await tester.hoverOnPageName(
|
||||
pageName,
|
||||
layout: ViewLayoutPB.Grid,
|
||||
onHover: () async => await tester.tapDeletePageButton(),
|
||||
onHover: () async => tester.tapDeletePageButton(),
|
||||
);
|
||||
final finder = find.descendant(
|
||||
of: find.byType(MentionPageBlock),
|
||||
@@ -114,7 +114,6 @@ Future<String> insertInlinePage(
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'insert_a_inline_page_${layout.name}',
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
// tap the first line of the document
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -24,9 +23,7 @@ void main() {
|
||||
await tester.tapGoButton();
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
|
||||
// tap the first line of the document
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
|
||||
-3
@@ -1,6 +1,5 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/outline/outline_block_component.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
@@ -17,7 +16,6 @@ void main() {
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'outline_test',
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
@@ -34,7 +32,6 @@ void main() {
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'outline_test',
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
|
||||
|
||||
+8
-22
@@ -1,7 +1,6 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -40,9 +39,7 @@ void main() {
|
||||
await tester.tapGoButton();
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
|
||||
// tap the first line of the document
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
@@ -86,9 +83,7 @@ void main() {
|
||||
await tester.tapGoButton();
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
|
||||
// tap the first line of the document
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
@@ -125,9 +120,7 @@ void main() {
|
||||
await tester.tapGoButton();
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
|
||||
// tap the first line of the document
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
@@ -162,9 +155,7 @@ void main() {
|
||||
await tester.tapGoButton();
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
|
||||
// tap the first line of the document
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
@@ -176,11 +167,8 @@ void main() {
|
||||
final toggleListIcon = find.byIcon(Icons.arrow_right);
|
||||
await tester.tapButton(toggleListIcon);
|
||||
|
||||
await tester.editor.updateSelection(
|
||||
Selection.collapsed(
|
||||
Position(path: [0], offset: 0),
|
||||
),
|
||||
);
|
||||
await tester.editor
|
||||
.updateSelection(Selection.collapsed(Position(path: [0])));
|
||||
await tester.ime.insertCharacter('\n');
|
||||
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
@@ -196,9 +184,7 @@ void main() {
|
||||
await tester.tapGoButton();
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
|
||||
// tap the first line of the document
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
@@ -209,7 +195,7 @@ void main() {
|
||||
|
||||
await tester.editor.updateSelection(
|
||||
Selection.collapsed(
|
||||
Position(path: [0], offset: 0),
|
||||
Position(path: [0]),
|
||||
),
|
||||
);
|
||||
await tester.simulateKeyEvent(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
@@ -18,10 +17,7 @@ void main() {
|
||||
|
||||
// create a new document called Sample
|
||||
const pageName = 'Sample';
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: pageName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
await tester.createNewPageWithNameUnderParent(name: pageName);
|
||||
|
||||
// focus on the editor
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
@@ -75,10 +71,7 @@ void main() {
|
||||
|
||||
// create a new document called Sample
|
||||
const pageName = 'Sample';
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: pageName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
await tester.createNewPageWithNameUnderParent(name: pageName);
|
||||
|
||||
// focus on the editor
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
|
||||
@@ -28,8 +28,8 @@ void main() {
|
||||
const service = TestWorkspaceService(TestWorkspace.emptyDocument);
|
||||
|
||||
group('Tests on a workspace with only an empty document', () {
|
||||
setUpAll(() async => await service.setUpAll());
|
||||
setUp(() async => await service.setUp());
|
||||
setUpAll(() async => service.setUpAll());
|
||||
setUp(() async => service.setUp());
|
||||
|
||||
testWidgets('/board shortcut creates a new board and view of the board',
|
||||
(tester) async {
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -33,7 +33,7 @@ void main() {
|
||||
File(path).writeAsStringSync(str);
|
||||
}
|
||||
// mock get files
|
||||
await mockPickFilePaths(
|
||||
mockPickFilePaths(
|
||||
paths: testFileNames
|
||||
.map((e) => p.join(context.applicationDataDirectory, e))
|
||||
.toList(),
|
||||
|
||||
@@ -15,8 +15,8 @@ void main() {
|
||||
const service = TestWorkspaceService(TestWorkspace.aiWorkSpace);
|
||||
|
||||
group('integration tests for open-ai smart menu', () {
|
||||
setUpAll(() async => await service.setUpAll());
|
||||
setUp(() async => await service.setUp());
|
||||
setUpAll(() async => service.setUpAll());
|
||||
setUp(() async => service.setUp());
|
||||
|
||||
testWidgets('testing selection on open-ai smart menu replace',
|
||||
(tester) async {
|
||||
@@ -59,7 +59,7 @@ void main() {
|
||||
|
||||
editorState.service.selectionService.updateSelection(
|
||||
Selection(
|
||||
start: Position(path: [1], offset: 0),
|
||||
start: Position(path: [1]),
|
||||
end: Position(path: [1], offset: 5),
|
||||
),
|
||||
);
|
||||
@@ -80,8 +80,8 @@ void main() {
|
||||
expect(
|
||||
editorState.service.selectionService.currentSelection.value,
|
||||
Selection(
|
||||
start: Position(path: [2], offset: 0),
|
||||
end: Position(path: [3], offset: 0),
|
||||
start: Position(path: [2]),
|
||||
end: Position(path: [3]),
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -99,7 +99,7 @@ Future<AppFlowyEditor> setUpOpenAITesting(WidgetTester tester) async {
|
||||
final Finder editor = find.byType(AppFlowyEditor);
|
||||
await tester.tap(editor);
|
||||
await tester.pumpAndSettle();
|
||||
return (tester.state(editor).widget as AppFlowyEditor);
|
||||
return tester.state(editor).widget as AppFlowyEditor;
|
||||
}
|
||||
|
||||
Future<void> mockOpenAIRepository() async {
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart';
|
||||
import 'package:appflowy/user/application/user_settings_service.dart';
|
||||
import 'package:appflowy/workspace/application/settings/date_time/date_format_ext.dart';
|
||||
import 'package:appflowy/workspace/presentation/notifications/widgets/notification_item.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/date_time.pbenum.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:calendar_view/calendar_view.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text_field.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../util/base.dart';
|
||||
import '../util/common_operations.dart';
|
||||
import '../util/editor_test_operations.dart';
|
||||
import '../util/expectation.dart';
|
||||
import '../util/keyboard.dart';
|
||||
|
||||
void main() {
|
||||
@@ -35,7 +38,7 @@ void main() {
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Trigger iline action menu and type 'remind tomorrow'
|
||||
// Trigger inline action menu and type 'remind tomorrow'
|
||||
final tomorrow = await _insertReminderTomorrow(tester);
|
||||
|
||||
Node node = tester.editor.getCurrentEditorState().getNodeAtPath([1])!;
|
||||
@@ -43,7 +46,7 @@ void main() {
|
||||
node.delta!.first.attributes![MentionBlockKeys.mention];
|
||||
|
||||
expect(node.type, 'paragraph');
|
||||
expect(mentionAttr['type'], MentionType.reminder.name);
|
||||
expect(mentionAttr['type'], MentionType.date.name);
|
||||
expect(mentionAttr['date'], tomorrow.toIso8601String());
|
||||
|
||||
await tester.tap(
|
||||
@@ -67,9 +70,57 @@ void main() {
|
||||
_dateWithTime(dateTimeSettings.timeFormat, tomorrow, time);
|
||||
|
||||
expect(node.type, 'paragraph');
|
||||
expect(mentionAttr['type'], MentionType.reminder.name);
|
||||
expect(mentionAttr['type'], MentionType.date.name);
|
||||
expect(mentionAttr['date'], tomorrowWithTime.toIso8601String());
|
||||
});
|
||||
|
||||
testWidgets('Add reminder for tomorrow, and navigate to it',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.editor.getCurrentEditorState().insertNewLine();
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Trigger inline action menu and type 'remind tomorrow'
|
||||
final tomorrow = await _insertReminderTomorrow(tester);
|
||||
|
||||
final Node node =
|
||||
tester.editor.getCurrentEditorState().getNodeAtPath([1])!;
|
||||
final Map<String, dynamic> mentionAttr =
|
||||
node.delta!.first.attributes![MentionBlockKeys.mention];
|
||||
|
||||
expect(node.type, 'paragraph');
|
||||
expect(mentionAttr['type'], MentionType.date.name);
|
||||
expect(mentionAttr['date'], tomorrow.toIso8601String());
|
||||
|
||||
// Create and Navigate to a new document
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Open "Upcoming" in Notification hub
|
||||
await tester.openNotificationHub(tabIndex: 1);
|
||||
|
||||
// Expect 1 notification
|
||||
tester.expectNotificationItems(1);
|
||||
|
||||
// Tap on the notification
|
||||
await tester.tap(find.byType(NotificationItem));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Expect node at path 1 to be the date/reminder
|
||||
expect(
|
||||
tester.editor
|
||||
.getCurrentEditorState()
|
||||
.getNodeAtPath([1])
|
||||
?.delta
|
||||
?.first
|
||||
.attributes?[MentionBlockKeys.mention]['type'],
|
||||
MentionType.date.name,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/rename_view_popover.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text_field.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../util/base.dart';
|
||||
import '../util/common_operations.dart';
|
||||
import '../util/keyboard.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('Rename current view item', () {
|
||||
testWidgets('by F2 shortcut', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
await FlowyTestKeyboard.simulateKeyDownEvent(
|
||||
[LogicalKeyboardKey.f2],
|
||||
tester: tester,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(RenameViewPopover), findsOneWidget);
|
||||
|
||||
await tester.enterText(
|
||||
find.descendant(
|
||||
of: find.byType(RenameViewPopover),
|
||||
matching: find.byType(FlowyTextField),
|
||||
),
|
||||
'hello',
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Dismiss rename popover
|
||||
await tester.tap(find.byType(AppFlowyEditor));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(SingleInnerViewItem),
|
||||
matching: find.text('hello'),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/folder/favorite_folder.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
@@ -34,14 +33,8 @@ void main() {
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: names[i],
|
||||
parentName: parentName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
tester.expectToSeePageName(
|
||||
names[i],
|
||||
parentName: parentName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
parentLayout: ViewLayoutPB.Document,
|
||||
);
|
||||
tester.expectToSeePageName(names[i], parentName: parentName);
|
||||
}
|
||||
|
||||
await tester.favoriteViewByName(gettingStarted);
|
||||
@@ -87,7 +80,6 @@ void main() {
|
||||
await tester.favoriteViewByName(gettingStarted);
|
||||
await tester.hoverOnPageName(
|
||||
gettingStarted,
|
||||
layout: ViewLayoutPB.Document,
|
||||
onHover: () async {
|
||||
await tester.renamePage(name);
|
||||
await tester.pumpAndSettle();
|
||||
@@ -116,7 +108,6 @@ void main() {
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: names[i],
|
||||
parentName: parentName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
tester.expectToSeePageName(names[i], parentName: parentName);
|
||||
}
|
||||
@@ -136,7 +127,6 @@ void main() {
|
||||
|
||||
await tester.hoverOnPageName(
|
||||
names[1],
|
||||
layout: ViewLayoutPB.Document,
|
||||
onHover: () async {
|
||||
await tester.tapDeletePageButton();
|
||||
await tester.pumpAndSettle();
|
||||
@@ -150,7 +140,6 @@ void main() {
|
||||
|
||||
await tester.hoverOnPageName(
|
||||
gettingStarted,
|
||||
layout: ViewLayoutPB.Document,
|
||||
onHover: () async {
|
||||
await tester.tapDeletePageButton();
|
||||
await tester.pumpAndSettle();
|
||||
@@ -194,7 +183,6 @@ void main() {
|
||||
await tester.favoriteViewByName(gettingStarted);
|
||||
await tester.hoverOnPageName(
|
||||
gettingStarted,
|
||||
layout: ViewLayoutPB.Document,
|
||||
useLast: false,
|
||||
onHover: () async {
|
||||
await tester.tapPageOptionButton();
|
||||
|
||||
@@ -82,7 +82,6 @@ void main() {
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: names[i],
|
||||
parentName: parentName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
tester.expectToSeePageName(names[i], parentName: parentName);
|
||||
}
|
||||
@@ -148,7 +147,7 @@ void main() {
|
||||
name: document,
|
||||
openAfterCreated: false,
|
||||
);
|
||||
tester.expectToSeePageName(document, layout: ViewLayoutPB.Document);
|
||||
tester.expectToSeePageName(document);
|
||||
|
||||
const grid = 'grid';
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
@@ -190,7 +189,6 @@ void main() {
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: grid,
|
||||
layout: ViewLayoutPB.Grid,
|
||||
openAfterCreated: true,
|
||||
);
|
||||
tester.expectToSeePageName(grid, layout: ViewLayoutPB.Grid);
|
||||
|
||||
|
||||
@@ -32,15 +32,9 @@ void main() {
|
||||
findsNothing,
|
||||
);
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: _documentName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
await tester.createNewPageWithNameUnderParent(name: _documentName);
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: _documentTwoName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
await tester.createNewPageWithNameUnderParent(name: _documentTwoName);
|
||||
|
||||
/// Open second menu item in a new tab
|
||||
await tester.openAppInNewTab(gettingStarted, ViewLayoutPB.Document);
|
||||
|
||||
@@ -11,7 +11,6 @@ import 'package:appflowy/user/application/auth/supabase_mock_auth_service.dart';
|
||||
import 'package:appflowy/user/presentation/presentation.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
@@ -34,12 +33,12 @@ extension AppFlowyTestBase on WidgetTester {
|
||||
String? pathExtension,
|
||||
// use to specify the application data directory, if not specified, a temporary directory will be used.
|
||||
String? dataDirectory,
|
||||
Size windowsSize = const Size(1600, 1200),
|
||||
Size windowSize = const Size(1600, 1200),
|
||||
AuthenticatorType? cloudType,
|
||||
String? email,
|
||||
}) async {
|
||||
// view.physicalSize = windowsSize;
|
||||
binding.setSurfaceSize(windowsSize);
|
||||
await binding.setSurfaceSize(windowSize);
|
||||
// addTearDown(() => binding.setSurfaceSize(null));
|
||||
|
||||
mockHotKeyManagerHandlers();
|
||||
@@ -78,14 +77,14 @@ extension AppFlowyTestBase on WidgetTester {
|
||||
await useLocal();
|
||||
break;
|
||||
case AuthenticatorType.supabase:
|
||||
await useSupabaseCloud();
|
||||
await useTestSupabaseCloud();
|
||||
getIt.unregister<AuthService>();
|
||||
getIt.registerFactory<AuthService>(
|
||||
() => SupabaseMockAuthService(),
|
||||
);
|
||||
break;
|
||||
case AuthenticatorType.appflowyCloudSelfHost:
|
||||
await useAppFlowyCloud();
|
||||
await useTestSelfHostedAppFlowyCloud();
|
||||
getIt.unregister<AuthService>();
|
||||
getIt.registerFactory<AuthService>(
|
||||
() => AppFlowyCloudMockAuthService(email: email),
|
||||
@@ -129,7 +128,7 @@ extension AppFlowyTestBase on WidgetTester {
|
||||
}
|
||||
|
||||
Future<void> waitForSeconds(int seconds) async {
|
||||
await Future.delayed((Duration(seconds: seconds)), () {});
|
||||
await Future.delayed(Duration(seconds: seconds), () {});
|
||||
}
|
||||
|
||||
Future<void> pumpUntilFound(
|
||||
@@ -249,20 +248,18 @@ extension AppFlowyFinderTestBase on CommonFinders {
|
||||
}
|
||||
|
||||
Future<void> useLocal() async {
|
||||
await setAuthenticatorType(AuthenticatorType.local);
|
||||
await useLocalServer();
|
||||
}
|
||||
|
||||
Future<void> useSupabaseCloud() async {
|
||||
await setAuthenticatorType(AuthenticatorType.supabase);
|
||||
await setSupbaseServer(
|
||||
Some(TestEnv.supabaseUrl),
|
||||
Some(TestEnv.supabaseAnonKey),
|
||||
Future<void> useTestSupabaseCloud() async {
|
||||
await useSupabaseCloud(
|
||||
url: TestEnv.supabaseUrl,
|
||||
anonKey: TestEnv.supabaseAnonKey,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> useAppFlowyCloud() async {
|
||||
await setAuthenticatorType(AuthenticatorType.appflowyCloudSelfHost);
|
||||
await setAppFlowyCloudUrl(Some(TestEnv.afCloudUrl));
|
||||
Future<void> useTestSelfHostedAppFlowyCloud() async {
|
||||
await useSelfHostedAppFlowyCloudWithURL(TestEnv.afCloudUrl);
|
||||
}
|
||||
|
||||
Future<String> mockApplicationDataStorage({
|
||||
|
||||
@@ -14,6 +14,9 @@ import 'package:appflowy/workspace/presentation/home/menu/view/draggable_view_it
|
||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_add_button.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_more_action_button.dart';
|
||||
import 'package:appflowy/workspace/presentation/notifications/widgets/flowy_tab.dart';
|
||||
import 'package:appflowy/workspace/presentation/notifications/widgets/notification_button.dart';
|
||||
import 'package:appflowy/workspace/presentation/notifications/widgets/notification_tab_bar.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_language_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/view_title_bar.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
@@ -394,7 +397,6 @@ extension CommonOperations on WidgetTester {
|
||||
await hoverOnPageName(
|
||||
name,
|
||||
layout: layout,
|
||||
useLast: true,
|
||||
onHover: () async {
|
||||
await tapFavoritePageButton();
|
||||
await pumpAndSettle();
|
||||
@@ -409,7 +411,6 @@ extension CommonOperations on WidgetTester {
|
||||
await hoverOnPageName(
|
||||
name,
|
||||
layout: layout,
|
||||
useLast: true,
|
||||
onHover: () async {
|
||||
await tapUnfavoritePageButton();
|
||||
await pumpAndSettle();
|
||||
@@ -493,6 +494,30 @@ extension CommonOperations on WidgetTester {
|
||||
await tapEmoji(icon);
|
||||
await pumpAndSettle();
|
||||
}
|
||||
|
||||
Future<void> openNotificationHub({
|
||||
int tabIndex = 0,
|
||||
}) async {
|
||||
final finder = find.descendant(
|
||||
of: find.byType(NotificationButton),
|
||||
matching: find.byWidgetPredicate(
|
||||
(widget) => widget is FlowySvg && widget.svg == FlowySvgs.clock_alarm_s,
|
||||
),
|
||||
);
|
||||
|
||||
await tap(finder);
|
||||
await pumpAndSettle();
|
||||
|
||||
if (tabIndex == 1) {
|
||||
final tabFinder = find.descendant(
|
||||
of: find.byType(NotificationTabBar),
|
||||
matching: find.byType(FlowyTabItem).at(1),
|
||||
);
|
||||
|
||||
await tap(tabFinder);
|
||||
await pumpAndSettle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ViewLayoutPBTest on ViewLayoutPB {
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
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';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/checklist.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/date.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/number.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/select_option.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/text.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/timestamp.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/url.dart';
|
||||
import 'package:appflowy/util/field_type_extension.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -26,7 +41,6 @@ import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/filte
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/footer/grid_footer.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/header/desktop_field_cell.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/header/field_editor.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/header/field_type_extension.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/header/field_type_list.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/header/type_option/date/date_time_format.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/row/row.dart';
|
||||
@@ -40,13 +54,12 @@ import 'package:appflowy/plugins/database/tab_bar/desktop/tab_bar_add_button.dar
|
||||
import 'package:appflowy/plugins/database/tab_bar/desktop/tab_bar_header.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/database_layout_ext.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/accessory/cell_accessory.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cells.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/checklist_cell/checklist_cell_editor.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/checklist_cell/checklist_progress_bar.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/date_cell/date_editor.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/select_option_cell/extension.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/select_option_cell/select_option_editor.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/select_option_cell/text_field.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell_editor/checklist_cell_editor.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell_editor/checklist_progress_bar.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell_editor/date_editor.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell_editor/extension.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell_editor/select_option_editor.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell_editor/select_option_text_field.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/row_action.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/row_banner.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/row_detail.dart';
|
||||
@@ -60,11 +73,10 @@ import 'package:appflowy/plugins/database/widgets/setting/setting_property_list.
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/clear_date_button.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/date_type_option_button.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/end_time_button.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/reminder_selector.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_board/appflowy_board.dart';
|
||||
import 'package:calendar_view/calendar_view.dart';
|
||||
@@ -76,6 +88,9 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:table_calendar/table_calendar.dart';
|
||||
|
||||
// Non-exported member of the table_calendar library
|
||||
import 'package:table_calendar/src/widgets/cell_content.dart';
|
||||
|
||||
import 'base.dart';
|
||||
import 'common_operations.dart';
|
||||
import 'expectation.dart';
|
||||
@@ -109,7 +124,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
File(path).writeAsStringSync(str);
|
||||
}
|
||||
// mock get files
|
||||
await mockPickFilePaths(
|
||||
mockPickFilePaths(
|
||||
paths: paths,
|
||||
);
|
||||
await tapDatabaseRawDataButton();
|
||||
@@ -138,7 +153,6 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
await pumpAndSettle();
|
||||
}
|
||||
|
||||
///
|
||||
Finder cellFinder(int rowIndex, FieldType fieldType, {int cellIndex = 0}) {
|
||||
final findRow = find.byType(GridRow, skipOffstage: false);
|
||||
final findCell = finderForFieldType(fieldType);
|
||||
@@ -170,10 +184,14 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
required bool isSelected,
|
||||
}) async {
|
||||
final cell = cellFinder(rowIndex, FieldType.Checkbox);
|
||||
var finder = find.byType(CheckboxCellUncheck);
|
||||
if (isSelected) {
|
||||
finder = find.byType(CheckboxCellCheck);
|
||||
}
|
||||
final finder = isSelected
|
||||
? find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is FlowySvg && widget.svg == FlowySvgs.check_filled_s,
|
||||
)
|
||||
: find.byWidgetPredicate(
|
||||
(widget) => widget is FlowySvg && widget.svg == FlowySvgs.uncheck_s,
|
||||
);
|
||||
|
||||
expect(
|
||||
find.descendant(
|
||||
@@ -190,30 +208,23 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
}) async {
|
||||
final cell = cellFinder(rowIndex, fieldType);
|
||||
expect(cell, findsOneWidget);
|
||||
await tapButton(cell, warnIfMissed: false);
|
||||
await tapButton(cell);
|
||||
}
|
||||
|
||||
/// The [fieldName] must be unique in the grid.
|
||||
Future<void> assertCellContent({
|
||||
void assertCellContent({
|
||||
required int rowIndex,
|
||||
required FieldType fieldType,
|
||||
required String content,
|
||||
int cellIndex = 0,
|
||||
}) async {
|
||||
}) {
|
||||
final findCell = cellFinder(rowIndex, fieldType, cellIndex: cellIndex);
|
||||
final findContent = find.descendant(
|
||||
of: findCell,
|
||||
matching: find.text(content),
|
||||
skipOffstage: false,
|
||||
);
|
||||
|
||||
final text = find.descendant(
|
||||
of: find.byType(TextField),
|
||||
matching: findContent,
|
||||
skipOffstage: false,
|
||||
);
|
||||
|
||||
expect(text, findsOneWidget);
|
||||
expect(findContent, findsOneWidget);
|
||||
}
|
||||
|
||||
Future<void> assertSingleSelectOption({
|
||||
@@ -255,10 +266,10 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
}
|
||||
|
||||
/// null percent means no progress bar should be found
|
||||
Future<void> assertChecklistCellInGrid({
|
||||
void assertChecklistCellInGrid({
|
||||
required int rowIndex,
|
||||
required double? percent,
|
||||
}) async {
|
||||
}) {
|
||||
final findCell = cellFinder(rowIndex, FieldType.Checklist);
|
||||
|
||||
if (percent == null) {
|
||||
@@ -271,44 +282,14 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
final finder = find.descendant(
|
||||
of: findCell,
|
||||
matching: find.byWidgetPredicate(
|
||||
(widget) {
|
||||
if (widget is ChecklistProgressBar) {
|
||||
return widget.percent == percent;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
(widget) =>
|
||||
widget is ChecklistProgressBar && widget.percent == percent,
|
||||
),
|
||||
);
|
||||
expect(finder, findsOneWidget);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> assertDateCellInGrid({
|
||||
required int rowIndex,
|
||||
required String content,
|
||||
}) async {
|
||||
final findRow = find.byType(GridRow, skipOffstage: false);
|
||||
final findCell = find.descendant(
|
||||
of: findRow.at(rowIndex),
|
||||
matching: find.byType(GridDateCell),
|
||||
skipOffstage: false,
|
||||
);
|
||||
|
||||
final text = find.descendant(
|
||||
of: findCell,
|
||||
matching: find.byWidgetPredicate(
|
||||
(widget) {
|
||||
if (widget is FlowyText) {
|
||||
return widget.text == content;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
),
|
||||
skipOffstage: false,
|
||||
);
|
||||
expect(text, findsOneWidget);
|
||||
}
|
||||
|
||||
Future<void> selectDay({
|
||||
required int content,
|
||||
}) async {
|
||||
@@ -343,6 +324,26 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
await tapButton(finder);
|
||||
}
|
||||
|
||||
Future<void> selectReminderOption(ReminderOption option) async {
|
||||
await tapButton(find.byType(ReminderSelector));
|
||||
|
||||
final finder = find.descendant(
|
||||
of: find.byType(FlowyButton),
|
||||
matching: find.textContaining(option.label),
|
||||
);
|
||||
|
||||
await tapButton(finder);
|
||||
}
|
||||
|
||||
Future<bool> selectLastDateInPicker() async {
|
||||
final finder = find.byType(CellContent).last;
|
||||
final w = widget(finder) as CellContent;
|
||||
|
||||
await tapButton(finder);
|
||||
|
||||
return w.isToday;
|
||||
}
|
||||
|
||||
Future<void> toggleDateRange() async {
|
||||
final findDateEditor = find.byType(EndTimeButton);
|
||||
final findToggle = find.byType(Toggle);
|
||||
@@ -353,15 +354,11 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
await tapButton(finder);
|
||||
}
|
||||
|
||||
Future<void> changeDateFormat() async {
|
||||
final findDateEditor = find.byType(DateCellEditor);
|
||||
final findDateTimeOptionButton = find.byType(DateTypeOptionButton);
|
||||
final finder = find.descendant(
|
||||
of: findDateEditor,
|
||||
matching: findDateTimeOptionButton,
|
||||
);
|
||||
await tapButton(finder);
|
||||
Future<void> tapChangeDateTimeFormatButton() async {
|
||||
await tapButton(find.byType(DateTypeOptionButton));
|
||||
}
|
||||
|
||||
Future<void> changeDateFormat() async {
|
||||
final findDateFormatButton = find.byType(DateFormatButton);
|
||||
await tapButton(findDateFormatButton);
|
||||
|
||||
@@ -370,14 +367,6 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
}
|
||||
|
||||
Future<void> changeTimeFormat() async {
|
||||
final findDateEditor = find.byType(DateCellEditor);
|
||||
final findDateTimeOptionButton = find.byType(DateTypeOptionButton);
|
||||
final finder = find.descendant(
|
||||
of: findDateEditor,
|
||||
matching: findDateTimeOptionButton,
|
||||
);
|
||||
await tapButton(finder);
|
||||
|
||||
final findDateFormatButton = find.byType(TimeFormatButton);
|
||||
await tapButton(findDateFormatButton);
|
||||
|
||||
@@ -491,7 +480,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
}
|
||||
|
||||
void assertChecklistEditorVisible({required bool visible}) {
|
||||
final editor = find.byType(GridChecklistCellEditor);
|
||||
final editor = find.byType(ChecklistCellEditor);
|
||||
if (visible) {
|
||||
expect(editor, findsOneWidget);
|
||||
} else {
|
||||
@@ -604,7 +593,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
}
|
||||
|
||||
Future<void> editTitleInRowDetailPage(String title) async {
|
||||
final titleField = find.byType(GridTextCell);
|
||||
final titleField = find.byType(EditableTextCell);
|
||||
await enterText(titleField, title);
|
||||
await pumpAndSettle();
|
||||
}
|
||||
@@ -622,11 +611,11 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
}
|
||||
|
||||
Future<void> openEmojiPicker() async {
|
||||
await tapButton(find.byType(EmojiPickerButton));
|
||||
await tapButton(find.byType(AddEmojiButton));
|
||||
}
|
||||
|
||||
Future<void> tapDateCellInRowDetailPage() async {
|
||||
final findDateCell = find.byType(GridDateCell);
|
||||
final findDateCell = find.byType(EditableDateCell);
|
||||
await tapButton(findDateCell);
|
||||
}
|
||||
|
||||
@@ -717,7 +706,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
|
||||
Future<void> scrollToRight(Finder find) async {
|
||||
final size = getSize(find);
|
||||
await drag(find, Offset(-size.width, 0));
|
||||
await drag(find, Offset(-size.width, 0), warnIfMissed: false);
|
||||
await pumpAndSettle(const Duration(milliseconds: 500));
|
||||
}
|
||||
|
||||
@@ -734,8 +723,33 @@ 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> tapEditPropertyButton() async {
|
||||
Future<void> tapEditFieldButton() async {
|
||||
await tapButtonWithName(LocaleKeys.grid_field_editProperty.tr());
|
||||
await pumpAndSettle(const Duration(milliseconds: 200));
|
||||
}
|
||||
@@ -749,7 +763,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
await tapButton(field);
|
||||
}
|
||||
|
||||
/// Should call [tapGridFieldWithName] first.
|
||||
/// A SimpleDialog must be shown first, e.g. when deleting a field.
|
||||
Future<void> tapDialogOkButton() async {
|
||||
final field = find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
@@ -817,7 +831,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
await tapButton(find.byType(RowDetailPageDuplicateButton));
|
||||
}
|
||||
|
||||
Future<void> tapTypeOptionButton() async {
|
||||
Future<void> tapSwitchFieldTypeButton() async {
|
||||
await tapButton(find.byType(SwitchFieldButton));
|
||||
}
|
||||
|
||||
@@ -825,18 +839,29 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
await sendKeyEvent(LogicalKeyboardKey.escape);
|
||||
}
|
||||
|
||||
/// Must call [tapTypeOptionButton] first.
|
||||
/// Must call [tapSwitchFieldTypeButton] first.
|
||||
Future<void> selectFieldType(FieldType fieldType) async {
|
||||
final fieldTypeCell = find.byType(FieldTypeCell);
|
||||
final fieldTypeButton = find.descendant(
|
||||
of: fieldTypeCell,
|
||||
matching: find.byWidgetPredicate(
|
||||
(widget) => widget is FlowyText && widget.text == fieldType.title(),
|
||||
(widget) => widget is FlowyText && widget.text == fieldType.i18n,
|
||||
),
|
||||
);
|
||||
await tapButton(fieldTypeButton);
|
||||
}
|
||||
|
||||
// Use in edit mode of FieldEditor
|
||||
void expectEmptyTypeOptionEditor() {
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(FieldTypeOptionEditor),
|
||||
matching: find.byType(TypeOptionSeparator),
|
||||
),
|
||||
findsNothing,
|
||||
);
|
||||
}
|
||||
|
||||
/// Each field has its own cell, so we can find the corresponding cell by
|
||||
/// the field type after create a new field.
|
||||
Future<void> findCellByFieldType(FieldType fieldType) async {
|
||||
@@ -886,14 +911,14 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
expect(widget.field.fieldType, fieldType);
|
||||
}
|
||||
|
||||
Future<void> findFieldWithName(String name) async {
|
||||
void findFieldWithName(String name) {
|
||||
final field = find.byWidgetPredicate(
|
||||
(widget) => widget is FieldCellButton && widget.field.name == name,
|
||||
);
|
||||
expect(field, findsOneWidget);
|
||||
}
|
||||
|
||||
Future<void> noFieldWithName(String name) async {
|
||||
void noFieldWithName(String name) {
|
||||
final field = find.byWidgetPredicate(
|
||||
(widget) => widget is FieldCellButton && widget.field.name == name,
|
||||
);
|
||||
@@ -958,7 +983,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
await scrollToRight(find.byType(GridPage));
|
||||
await tapNewPropertyButton();
|
||||
await renameField(name);
|
||||
await tapTypeOptionButton();
|
||||
await tapSwitchFieldTypeButton();
|
||||
await selectFieldType(fieldType);
|
||||
await dismissFieldEditor();
|
||||
}
|
||||
@@ -1045,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(
|
||||
@@ -1116,7 +1169,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
skipOffstage: false,
|
||||
);
|
||||
expect(findCell, findsOneWidget);
|
||||
await tapButton(findCell, warnIfMissed: false);
|
||||
await tapButton(findCell);
|
||||
}
|
||||
|
||||
Future<void> tapCheckedButtonOnCheckboxFilter() async {
|
||||
@@ -1386,7 +1439,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
Future<void> dragDropRescheduleCalendarEvent() async {
|
||||
final findEventCard = find.byType(EventCard);
|
||||
await drag(findEventCard.first, const Offset(0, 300));
|
||||
await pumpAndSettle();
|
||||
await pumpAndSettle(const Duration(microseconds: 300));
|
||||
}
|
||||
|
||||
Future<void> openUnscheduledEventsPopup() async {
|
||||
@@ -1565,7 +1618,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
await tapButton(okButton);
|
||||
}
|
||||
|
||||
Future<void> assertCurrentDatabaseTagIs(DatabaseLayoutPB layout) async {
|
||||
void assertCurrentDatabaseTagIs(DatabaseLayoutPB layout) {
|
||||
switch (layout) {
|
||||
case DatabaseLayoutPB.Board:
|
||||
expect(find.byType(BoardPage), findsOneWidget);
|
||||
@@ -1603,6 +1656,23 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
await tapButtonWithName(LocaleKeys.importPanel_database.tr());
|
||||
}
|
||||
|
||||
// Use in edit mode of FieldEditor
|
||||
Future<void> changeNumberFieldFormat() async {
|
||||
final changeFormatButton = find.descendant(
|
||||
of: find.byType(FieldTypeOptionEditor),
|
||||
matching: find.text("Number"),
|
||||
);
|
||||
await tapButton(changeFormatButton);
|
||||
|
||||
await tapButton(
|
||||
find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is NumberFormatCell && widget.format == NumberFormatPB.USD,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Use in edit mode of FieldEditor
|
||||
Future<void> tapAddSelectOptionButton() async {
|
||||
await tapButtonWithName(LocaleKeys.grid_field_addSelectOption.tr());
|
||||
}
|
||||
@@ -1636,24 +1706,45 @@ Finder finderForDatabaseLayoutType(DatabaseLayoutPB layout) {
|
||||
Finder finderForFieldType(FieldType fieldType) {
|
||||
switch (fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
return find.byType(GridCheckboxCell, skipOffstage: false);
|
||||
return find.byType(EditableCheckboxCell, skipOffstage: false);
|
||||
case FieldType.DateTime:
|
||||
return find.byType(GridDateCell, skipOffstage: false);
|
||||
return find.byType(EditableDateCell, skipOffstage: false);
|
||||
case FieldType.LastEditedTime:
|
||||
return find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is EditableTimestampCell &&
|
||||
widget.fieldType == FieldType.LastEditedTime,
|
||||
skipOffstage: false,
|
||||
);
|
||||
case FieldType.CreatedTime:
|
||||
return find.byType(GridTimestampCell, skipOffstage: false);
|
||||
return find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is EditableTimestampCell &&
|
||||
widget.fieldType == FieldType.CreatedTime,
|
||||
skipOffstage: false,
|
||||
);
|
||||
case FieldType.SingleSelect:
|
||||
return find.byType(GridSingleSelectCell, skipOffstage: false);
|
||||
return find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is EditableSelectOptionCell &&
|
||||
widget.fieldType == FieldType.SingleSelect,
|
||||
skipOffstage: false,
|
||||
);
|
||||
case FieldType.MultiSelect:
|
||||
return find.byType(GridMultiSelectCell, skipOffstage: false);
|
||||
return find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is EditableSelectOptionCell &&
|
||||
widget.fieldType == FieldType.MultiSelect,
|
||||
skipOffstage: false,
|
||||
);
|
||||
case FieldType.Checklist:
|
||||
return find.byType(GridChecklistCell, skipOffstage: false);
|
||||
return find.byType(EditableChecklistCell, skipOffstage: false);
|
||||
case FieldType.Number:
|
||||
return find.byType(GridNumberCell, skipOffstage: false);
|
||||
return find.byType(EditableNumberCell, skipOffstage: false);
|
||||
case FieldType.RichText:
|
||||
return find.byType(GridTextCell, skipOffstage: false);
|
||||
return find.byType(EditableTextCell, skipOffstage: false);
|
||||
case FieldType.URL:
|
||||
return find.byType(GridURLCell, skipOffstage: false);
|
||||
return find.byType(EditableURLCell, skipOffstage: false);
|
||||
default:
|
||||
throw Exception('Unknown field type: $fieldType');
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
@@ -6,9 +7,9 @@ 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/header/cover_editor.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/custom_cover_picker.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';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/embed_image_url_widget.dart';
|
||||
import 'package:appflowy/plugins/inline_actions/widgets/inline_actions_handler.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@@ -121,19 +122,25 @@ class EditorOperations {
|
||||
}
|
||||
|
||||
Future<void> addNetworkImageCover(String imageUrl) async {
|
||||
final findNewImageButton = find.byType(NewCustomCoverButton);
|
||||
await tester.tapButton(findNewImageButton);
|
||||
final embedLinkButton = find.findTextInFlowyText(
|
||||
LocaleKeys.document_imageBlock_embedLink_label.tr(),
|
||||
);
|
||||
await tester.tapButton(embedLinkButton);
|
||||
|
||||
final imageUrlTextField = find.descendant(
|
||||
of: find.byType(NetworkImageUrlInput),
|
||||
of: find.byType(EmbedImageUrlWidget),
|
||||
matching: find.byType(TextField),
|
||||
);
|
||||
await tester.enterText(imageUrlTextField, imageUrl);
|
||||
await tester.tapButtonWithName(
|
||||
LocaleKeys.document_plugins_cover_add.tr(),
|
||||
);
|
||||
await tester.tapButtonWithName(
|
||||
LocaleKeys.document_plugins_cover_saveToGallery.tr(),
|
||||
final textField = tester.widget<TextField>(imageUrlTextField);
|
||||
textField.controller?.text = imageUrl;
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tapButton(
|
||||
find.descendant(
|
||||
of: find.byType(EmbedImageUrlWidget),
|
||||
matching: find.findTextInFlowyText(
|
||||
LocaleKeys.document_imageBlock_embedLink_label.tr(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -196,9 +203,11 @@ class EditorOperations {
|
||||
/// Update the editor's selection
|
||||
Future<void> updateSelection(Selection selection) async {
|
||||
final editorState = getCurrentEditorState();
|
||||
editorState.updateSelectionWithReason(
|
||||
selection,
|
||||
reason: SelectionUpdateReason.uiEvent,
|
||||
unawaited(
|
||||
editorState.updateSelectionWithReason(
|
||||
selection,
|
||||
reason: SelectionUpdateReason.uiEvent,
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 200));
|
||||
}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/row_detail.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/banner.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';
|
||||
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/home_stack.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
|
||||
import 'package:appflowy/workspace/presentation/notifications/widgets/notification_item.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/reminder_selector.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/view_title_bar.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';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'util.dart';
|
||||
@@ -242,4 +246,29 @@ extension Expectation on WidgetTester {
|
||||
);
|
||||
expect(icon, findsOneWidget);
|
||||
}
|
||||
|
||||
void expectSelectedReminder(ReminderOption option) {
|
||||
final findSelectedText = find.descendant(
|
||||
of: find.byType(ReminderSelector),
|
||||
matching: find.text(option.label),
|
||||
);
|
||||
|
||||
expect(findSelectedText, findsOneWidget);
|
||||
}
|
||||
|
||||
void expectNotificationItems(int amount) {
|
||||
final findItems = find.byType(NotificationItem);
|
||||
|
||||
expect(findItems, findsNWidgets(amount));
|
||||
}
|
||||
|
||||
void expectToSeeRowDetailsPageDialog() {
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(RowDetailPage),
|
||||
matching: find.byType(SimpleDialog),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,6 @@ class IMESimulator {
|
||||
selection: TextSelection.collapsed(
|
||||
offset: value.selection.baseOffset + 1,
|
||||
),
|
||||
composing: TextRange.empty,
|
||||
);
|
||||
client.updateEditingValue(textEditingValue);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
@@ -74,9 +74,7 @@ Future<String> mockSaveFilePath(
|
||||
return path;
|
||||
}
|
||||
|
||||
Future<List<String>> mockPickFilePaths({
|
||||
required List<String> paths,
|
||||
}) async {
|
||||
List<String> mockPickFilePaths({required List<String> paths}) {
|
||||
getIt.unregister<FilePickerService>();
|
||||
getIt.registerFactory<FilePickerService>(
|
||||
() => MockFilePicker(
|
||||
|
||||
@@ -48,9 +48,6 @@ PODS:
|
||||
- fluttertoast (0.0.2):
|
||||
- Flutter
|
||||
- Toast
|
||||
- FMDB (2.7.5):
|
||||
- FMDB/standard (= 2.7.5)
|
||||
- FMDB/standard (2.7.5)
|
||||
- image_gallery_saver (2.0.2):
|
||||
- Flutter
|
||||
- image_picker_ios (0.0.1):
|
||||
@@ -72,22 +69,20 @@ PODS:
|
||||
- SDWebImage (5.14.2):
|
||||
- SDWebImage/Core (= 5.14.2)
|
||||
- SDWebImage/Core (5.14.2)
|
||||
- share_plus (0.0.1):
|
||||
- Flutter
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- sign_in_with_apple (0.0.1):
|
||||
- Flutter
|
||||
- sqflite (0.0.3):
|
||||
- Flutter
|
||||
- FMDB (>= 2.7.5)
|
||||
- FlutterMacOS
|
||||
- super_native_extensions (0.0.1):
|
||||
- Flutter
|
||||
- SwiftyGif (5.4.3)
|
||||
- Toast (4.0.0)
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
- webview_flutter_wkwebview (0.0.1):
|
||||
- Flutter
|
||||
|
||||
DEPENDENCIES:
|
||||
- app_links (from `.symlinks/plugins/app_links/ios`)
|
||||
@@ -106,18 +101,16 @@ DEPENDENCIES:
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- rich_clipboard_ios (from `.symlinks/plugins/rich_clipboard_ios/ios`)
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sign_in_with_apple (from `.symlinks/plugins/sign_in_with_apple/ios`)
|
||||
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
||||
- sqflite (from `.symlinks/plugins/sqflite/darwin`)
|
||||
- super_native_extensions (from `.symlinks/plugins/super_native_extensions/ios`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- DKImagePickerController
|
||||
- DKPhotoGallery
|
||||
- FMDB
|
||||
- ReachabilitySwift
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
@@ -156,18 +149,16 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||
rich_clipboard_ios:
|
||||
:path: ".symlinks/plugins/rich_clipboard_ios/ios"
|
||||
share_plus:
|
||||
:path: ".symlinks/plugins/share_plus/ios"
|
||||
shared_preferences_foundation:
|
||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||
sign_in_with_apple:
|
||||
:path: ".symlinks/plugins/sign_in_with_apple/ios"
|
||||
sqflite:
|
||||
:path: ".symlinks/plugins/sqflite/ios"
|
||||
:path: ".symlinks/plugins/sqflite/darwin"
|
||||
super_native_extensions:
|
||||
:path: ".symlinks/plugins/super_native_extensions/ios"
|
||||
url_launcher_ios:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
webview_flutter_wkwebview:
|
||||
:path: ".symlinks/plugins/webview_flutter_wkwebview/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
app_links: 5ef33d0d295a89d9d16bb81b0e3b0d5f70d6c875
|
||||
@@ -180,25 +171,23 @@ SPEC CHECKSUMS:
|
||||
flowy_infra_ui: 0455e1fa8c51885aa1437848e361e99419f34ebc
|
||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||
fluttertoast: fafc4fa4d01a6a9e4f772ecd190ffa525e9e2d9c
|
||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||
image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb
|
||||
image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5
|
||||
image_picker_ios: 99dfe1854b4fa34d0364e74a78448a0151025425
|
||||
integration_test: 13825b8a9334a850581300559b8839134b124670
|
||||
irondash_engine_context: 3458bf979b90d616ffb8ae03a150bafe2e860cc9
|
||||
keyboard_height_plugin: 43fa8bba20fd5c4fdeed5076466b8b9d43cc6b86
|
||||
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
|
||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
||||
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
|
||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||
rich_clipboard_ios: 7588abe18f881a6d0e9ec0b12e51cae2761e8942
|
||||
SDWebImage: b9a731e1d6307f44ca703b3976d18c24ca561e84
|
||||
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
|
||||
sign_in_with_apple: f3bf75217ea4c2c8b91823f225d70230119b8440
|
||||
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
|
||||
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
|
||||
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
|
||||
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
||||
super_native_extensions: 4916b3c627a9c7fffdc48a23a9eca0b1ac228fa7
|
||||
SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780
|
||||
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
|
||||
url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b
|
||||
webview_flutter_wkwebview: 2e2d318f21a5e036e2c3f26171342e95908bd60a
|
||||
url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812
|
||||
|
||||
PODFILE CHECKSUM: 8c681999c7764593c94846b2a64b44d86f7a27ac
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ class MoveWindowDetectorState extends State<MoveWindowDetector> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!Platform.isMacOS) {
|
||||
return widget.child ?? Container();
|
||||
return widget.child ?? const SizedBox.shrink();
|
||||
}
|
||||
return GestureDetector(
|
||||
// https://stackoverflow.com/questions/52965799/flutter-gesturedetector-not-working-with-containers-in-stack
|
||||
|
||||
@@ -7,21 +7,21 @@ import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class NetworkListener {
|
||||
final Connectivity _connectivity = Connectivity();
|
||||
late StreamSubscription<ConnectivityResult> _connectivitySubscription;
|
||||
|
||||
NetworkListener() {
|
||||
_connectivitySubscription =
|
||||
_connectivity.onConnectivityChanged.listen(_updateConnectionStatus);
|
||||
}
|
||||
|
||||
final Connectivity _connectivity = Connectivity();
|
||||
late StreamSubscription<ConnectivityResult> _connectivitySubscription;
|
||||
|
||||
Future<void> start() async {
|
||||
late ConnectivityResult result;
|
||||
// Platform messages may fail, so we use a try/catch PlatformException.
|
||||
try {
|
||||
result = await _connectivity.checkConnectivity();
|
||||
} on PlatformException catch (e) {
|
||||
Log.error('Couldn\'t check connectivity status. $e');
|
||||
Log.error("Couldn't check connectivity status. $e");
|
||||
return;
|
||||
}
|
||||
return _updateConnectionStatus(result);
|
||||
@@ -50,11 +50,8 @@ class NetworkListener {
|
||||
}
|
||||
}();
|
||||
final state = NetworkStatePB.create()..ty = networkType;
|
||||
UserEventUpdateNetworkState(state).send().then((result) {
|
||||
result.fold(
|
||||
(l) {},
|
||||
(e) => Log.error(e),
|
||||
);
|
||||
return UserEventUpdateNetworkState(state).send().then((result) {
|
||||
result.fold((l) {}, (e) => Log.error(e));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -31,9 +31,6 @@ typedef FolderNotificationHandler = Function(
|
||||
);
|
||||
|
||||
class FolderNotificationListener {
|
||||
StreamSubscription<SubscribeObject>? _subscription;
|
||||
FolderNotificationParser? _parser;
|
||||
|
||||
FolderNotificationListener({
|
||||
required String objectId,
|
||||
required FolderNotificationHandler handler,
|
||||
@@ -45,6 +42,9 @@ class FolderNotificationListener {
|
||||
RustStreamReceiver.listen((observable) => _parser?.parse(observable));
|
||||
}
|
||||
|
||||
FolderNotificationParser? _parser;
|
||||
StreamSubscription<SubscribeObject>? _subscription;
|
||||
|
||||
Future<void> stop() async {
|
||||
_parser = null;
|
||||
await _subscription?.cancel();
|
||||
|
||||
@@ -31,9 +31,6 @@ typedef DatabaseNotificationHandler = Function(
|
||||
);
|
||||
|
||||
class DatabaseNotificationListener {
|
||||
StreamSubscription<SubscribeObject>? _subscription;
|
||||
DatabaseNotificationParser? _parser;
|
||||
|
||||
DatabaseNotificationListener({
|
||||
required String objectId,
|
||||
required DatabaseNotificationHandler handler,
|
||||
@@ -42,6 +39,9 @@ class DatabaseNotificationListener {
|
||||
RustStreamReceiver.listen((observable) => _parser?.parse(observable));
|
||||
}
|
||||
|
||||
DatabaseNotificationParser? _parser;
|
||||
StreamSubscription<SubscribeObject>? _subscription;
|
||||
|
||||
Future<void> stop() async {
|
||||
_parser = null;
|
||||
await _subscription?.cancel();
|
||||
|
||||
@@ -3,18 +3,18 @@ import 'package:appflowy_backend/protobuf/flowy-notification/protobuf.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
|
||||
class NotificationParser<T, E> {
|
||||
String? id;
|
||||
void Function(T, Either<Uint8List, E>) callback;
|
||||
|
||||
T? Function(int) tyParser;
|
||||
E Function(Uint8List) errorParser;
|
||||
|
||||
NotificationParser({
|
||||
this.id,
|
||||
required this.callback,
|
||||
required this.errorParser,
|
||||
required this.tyParser,
|
||||
});
|
||||
|
||||
String? id;
|
||||
void Function(T, Either<Uint8List, E>) callback;
|
||||
E Function(Uint8List) errorParser;
|
||||
T? Function(int) tyParser;
|
||||
|
||||
void parse(SubscribeObject subject) {
|
||||
if (id != null) {
|
||||
if (subject.id != id) {
|
||||
|
||||
@@ -31,9 +31,6 @@ typedef UserNotificationHandler = Function(
|
||||
);
|
||||
|
||||
class UserNotificationListener {
|
||||
StreamSubscription<SubscribeObject>? _subscription;
|
||||
UserNotificationParser? _parser;
|
||||
|
||||
UserNotificationListener({
|
||||
required String objectId,
|
||||
required UserNotificationHandler handler,
|
||||
@@ -42,6 +39,9 @@ class UserNotificationListener {
|
||||
RustStreamReceiver.listen((observable) => _parser?.parse(observable));
|
||||
}
|
||||
|
||||
UserNotificationParser? _parser;
|
||||
StreamSubscription<SubscribeObject>? _subscription;
|
||||
|
||||
Future<void> stop() async {
|
||||
_parser = null;
|
||||
await _subscription?.cancel();
|
||||
|
||||
+19
-17
@@ -5,17 +5,9 @@ part 'backend_env.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class AppFlowyConfiguration {
|
||||
final String root;
|
||||
final String custom_app_path;
|
||||
final String origin_app_path;
|
||||
final String device_id;
|
||||
final int authenticator_type;
|
||||
final SupabaseConfiguration supabase_config;
|
||||
final AppFlowyCloudConfiguration appflowy_cloud_config;
|
||||
final Map<String, String> envs;
|
||||
|
||||
AppFlowyConfiguration({
|
||||
required this.root,
|
||||
required this.app_version,
|
||||
required this.custom_app_path,
|
||||
required this.origin_app_path,
|
||||
required this.device_id,
|
||||
@@ -28,15 +20,21 @@ class AppFlowyConfiguration {
|
||||
factory AppFlowyConfiguration.fromJson(Map<String, dynamic> json) =>
|
||||
_$AppFlowyConfigurationFromJson(json);
|
||||
|
||||
final String root;
|
||||
final String app_version;
|
||||
final String custom_app_path;
|
||||
final String origin_app_path;
|
||||
final String device_id;
|
||||
final int authenticator_type;
|
||||
final SupabaseConfiguration supabase_config;
|
||||
final AppFlowyCloudConfiguration appflowy_cloud_config;
|
||||
final Map<String, String> envs;
|
||||
|
||||
Map<String, dynamic> toJson() => _$AppFlowyConfigurationToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class SupabaseConfiguration {
|
||||
/// Indicates whether the sync feature is enabled.
|
||||
final String url;
|
||||
final String anon_key;
|
||||
|
||||
SupabaseConfiguration({
|
||||
required this.url,
|
||||
required this.anon_key,
|
||||
@@ -45,6 +43,10 @@ class SupabaseConfiguration {
|
||||
factory SupabaseConfiguration.fromJson(Map<String, dynamic> json) =>
|
||||
_$SupabaseConfigurationFromJson(json);
|
||||
|
||||
/// Indicates whether the sync feature is enabled.
|
||||
final String url;
|
||||
final String anon_key;
|
||||
|
||||
Map<String, dynamic> toJson() => _$SupabaseConfigurationToJson(this);
|
||||
|
||||
static SupabaseConfiguration defaultConfig() {
|
||||
@@ -61,10 +63,6 @@ class SupabaseConfiguration {
|
||||
|
||||
@JsonSerializable()
|
||||
class AppFlowyCloudConfiguration {
|
||||
final String base_url;
|
||||
final String ws_base_url;
|
||||
final String gotrue_url;
|
||||
|
||||
AppFlowyCloudConfiguration({
|
||||
required this.base_url,
|
||||
required this.ws_base_url,
|
||||
@@ -74,6 +72,10 @@ class AppFlowyCloudConfiguration {
|
||||
factory AppFlowyCloudConfiguration.fromJson(Map<String, dynamic> json) =>
|
||||
_$AppFlowyCloudConfigurationFromJson(json);
|
||||
|
||||
final String base_url;
|
||||
final String ws_base_url;
|
||||
final String gotrue_url;
|
||||
|
||||
Map<String, dynamic> toJson() => _$AppFlowyCloudConfigurationToJson(this);
|
||||
|
||||
static AppFlowyCloudConfiguration defaultConfig() {
|
||||
|
||||
+49
-16
@@ -17,7 +17,7 @@ import 'package:dartz/dartz.dart';
|
||||
/// - `CloudType.local` is stored as "0".
|
||||
/// - `CloudType.supabase` is stored as "1".
|
||||
/// - `CloudType.appflowyCloud` is stored as "2".
|
||||
Future<void> setAuthenticatorType(AuthenticatorType ty) async {
|
||||
Future<void> _setAuthenticatorType(AuthenticatorType ty) async {
|
||||
switch (ty) {
|
||||
case AuthenticatorType.local:
|
||||
await getIt<KeyValueStorage>().set(KVKeys.kCloudType, 0.toString());
|
||||
@@ -52,8 +52,12 @@ const String kAppflowyCloudUrl = "https://beta.appflowy.cloud";
|
||||
///
|
||||
Future<AuthenticatorType> getAuthenticatorType() async {
|
||||
final value = await getIt<KeyValueStorage>().get(KVKeys.kCloudType);
|
||||
if (value.isNone() && integrationMode().isRelease) {
|
||||
await setAuthenticatorType(AuthenticatorType.appflowyCloud);
|
||||
if (value.isNone() && !integrationMode().isUnitTest) {
|
||||
// if the cloud type is not set, then set it to AppFlowy Cloud as default.
|
||||
await useAppFlowyBetaCloudWithURL(
|
||||
kAppflowyCloudUrl,
|
||||
AuthenticatorType.appflowyCloud,
|
||||
);
|
||||
return AuthenticatorType.appflowyCloud;
|
||||
}
|
||||
|
||||
@@ -69,7 +73,10 @@ Future<AuthenticatorType> getAuthenticatorType() async {
|
||||
case "4":
|
||||
return AuthenticatorType.appflowyCloudDevelop;
|
||||
default:
|
||||
await setAuthenticatorType(AuthenticatorType.appflowyCloud);
|
||||
await useAppFlowyBetaCloudWithURL(
|
||||
kAppflowyCloudUrl,
|
||||
AuthenticatorType.appflowyCloud,
|
||||
);
|
||||
return AuthenticatorType.appflowyCloud;
|
||||
}
|
||||
}
|
||||
@@ -170,25 +177,50 @@ AuthenticatorType currentCloudType() {
|
||||
return getIt<AppFlowyCloudSharedEnv>().authenticatorType;
|
||||
}
|
||||
|
||||
Future<void> setAppFlowyCloudUrl(Option<String> url) async {
|
||||
Future<void> _setAppFlowyCloudUrl(Option<String> url) async {
|
||||
await url.fold(
|
||||
() => getIt<KeyValueStorage>().set(KVKeys.kAppflowyCloudBaseURL, ""),
|
||||
(s) => getIt<KeyValueStorage>().set(KVKeys.kAppflowyCloudBaseURL, s),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> useSelfHostedAppFlowyCloudWithURL(String url) async {
|
||||
await _setAuthenticatorType(AuthenticatorType.appflowyCloudSelfHost);
|
||||
await _setAppFlowyCloudUrl(Some(url));
|
||||
}
|
||||
|
||||
Future<void> useAppFlowyBetaCloudWithURL(
|
||||
String url,
|
||||
AuthenticatorType authenticatorType,
|
||||
) async {
|
||||
await _setAuthenticatorType(authenticatorType);
|
||||
await _setAppFlowyCloudUrl(Some(url));
|
||||
}
|
||||
|
||||
Future<void> useLocalServer() async {
|
||||
await _setAuthenticatorType(AuthenticatorType.local);
|
||||
}
|
||||
|
||||
Future<void> useSupabaseCloud({
|
||||
required String url,
|
||||
required String anonKey,
|
||||
}) async {
|
||||
await _setAuthenticatorType(AuthenticatorType.supabase);
|
||||
await setSupbaseServer(Some(url), Some(anonKey));
|
||||
}
|
||||
|
||||
/// Use getIt<AppFlowyCloudSharedEnv>() to get the shared environment.
|
||||
class AppFlowyCloudSharedEnv {
|
||||
final AuthenticatorType _authenticatorType;
|
||||
final AppFlowyCloudConfiguration appflowyCloudConfig;
|
||||
final SupabaseConfiguration supabaseConfig;
|
||||
|
||||
AppFlowyCloudSharedEnv({
|
||||
required AuthenticatorType authenticatorType,
|
||||
required this.appflowyCloudConfig,
|
||||
required this.supabaseConfig,
|
||||
}) : _authenticatorType = authenticatorType;
|
||||
|
||||
final AuthenticatorType _authenticatorType;
|
||||
final AppFlowyCloudConfiguration appflowyCloudConfig;
|
||||
final SupabaseConfiguration supabaseConfig;
|
||||
|
||||
AuthenticatorType get authenticatorType => _authenticatorType;
|
||||
|
||||
static Future<AppFlowyCloudSharedEnv> fromEnv() async {
|
||||
@@ -197,12 +229,13 @@ class AppFlowyCloudSharedEnv {
|
||||
// Use the custom cloud configuration.
|
||||
var authenticatorType = await getAuthenticatorType();
|
||||
|
||||
final appflowyCloudConfig = authenticatorType.isLocal
|
||||
? AppFlowyCloudConfiguration.defaultConfig()
|
||||
: await getAppFlowyCloudConfig(authenticatorType);
|
||||
final supabaseCloudConfig = authenticatorType.isLocal
|
||||
? SupabaseConfiguration.defaultConfig()
|
||||
: await getSupabaseCloudConfig();
|
||||
final appflowyCloudConfig = authenticatorType.isAppFlowyCloudEnabled
|
||||
? await getAppFlowyCloudConfig(authenticatorType)
|
||||
: AppFlowyCloudConfiguration.defaultConfig();
|
||||
|
||||
final supabaseCloudConfig = authenticatorType.isSupabaseEnabled
|
||||
? await getSupabaseCloudConfig()
|
||||
: SupabaseConfiguration.defaultConfig();
|
||||
|
||||
// In the backend, the value '2' represents the use of AppFlowy Cloud. However, in the frontend,
|
||||
// we distinguish between [AuthenticatorType.appflowyCloudSelfHost] and [AuthenticatorType.appflowyCloud].
|
||||
@@ -282,7 +315,7 @@ Future<String> getAppFlowyCloudUrl() async {
|
||||
final result =
|
||||
await getIt<KeyValueStorage>().get(KVKeys.kAppflowyCloudBaseURL);
|
||||
return result.fold(
|
||||
() => "https://beta.appflowy.cloud",
|
||||
() => kAppflowyCloudUrl,
|
||||
(url) => url,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:appflowy/mobile/presentation/database/board/mobile_board_screen.dart';
|
||||
import 'package:appflowy/mobile/presentation/database/mobile_calendar_screen.dart';
|
||||
import 'package:appflowy/mobile/presentation/database/mobile_grid_screen.dart';
|
||||
@@ -8,11 +10,11 @@ import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
extension MobileRouter on BuildContext {
|
||||
Future<void> pushView(ViewPB view) async {
|
||||
push(
|
||||
Future<void> pushView(ViewPB view, [Map<String, dynamic>? arguments]) async {
|
||||
await push(
|
||||
Uri(
|
||||
path: view.routeName,
|
||||
queryParameters: view.queryParameters,
|
||||
queryParameters: view.queryParameters(arguments),
|
||||
).toString(),
|
||||
).then((value) {
|
||||
RecentService().updateRecentViews([view.id], true);
|
||||
@@ -36,7 +38,7 @@ extension on ViewPB {
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> get queryParameters {
|
||||
Map<String, dynamic> queryParameters([Map<String, dynamic>? arguments]) {
|
||||
switch (layout) {
|
||||
case ViewLayoutPB.Document:
|
||||
return {
|
||||
@@ -47,6 +49,7 @@ extension on ViewPB {
|
||||
return {
|
||||
MobileGridScreen.viewId: id,
|
||||
MobileGridScreen.viewTitle: name,
|
||||
MobileGridScreen.viewArgs: jsonEncode(arguments),
|
||||
};
|
||||
case ViewLayoutPB.Calendar:
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum FlowyAppBarLeadingType {
|
||||
back,
|
||||
close,
|
||||
cancel;
|
||||
|
||||
Widget getWidget(VoidCallback? onTap) {
|
||||
switch (this) {
|
||||
case FlowyAppBarLeadingType.back:
|
||||
return AppBarBackButton(onTap: onTap);
|
||||
case FlowyAppBarLeadingType.close:
|
||||
return AppBarCloseButton(onTap: onTap);
|
||||
case FlowyAppBarLeadingType.cancel:
|
||||
return AppBarCancelButton(onTap: onTap);
|
||||
}
|
||||
}
|
||||
|
||||
double? get width {
|
||||
switch (this) {
|
||||
case FlowyAppBarLeadingType.back:
|
||||
return 40.0;
|
||||
case FlowyAppBarLeadingType.close:
|
||||
return 40.0;
|
||||
case FlowyAppBarLeadingType.cancel:
|
||||
return 120;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FlowyAppBar extends AppBar {
|
||||
FlowyAppBar({
|
||||
super.key,
|
||||
super.actions,
|
||||
Widget? title,
|
||||
String? titleText,
|
||||
FlowyAppBarLeadingType leadingType = FlowyAppBarLeadingType.back,
|
||||
super.centerTitle,
|
||||
VoidCallback? onTapLeading,
|
||||
bool showDivider = true,
|
||||
}) : super(
|
||||
title: title ??
|
||||
FlowyText(
|
||||
titleText ?? '',
|
||||
fontSize: 15.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
titleSpacing: 0,
|
||||
elevation: 0,
|
||||
leading: leadingType.getWidget(onTapLeading),
|
||||
leadingWidth: leadingType.width,
|
||||
toolbarHeight: 44.0,
|
||||
bottom: showDivider
|
||||
? const PreferredSize(
|
||||
preferredSize: Size.fromHeight(0.5),
|
||||
child: Divider(
|
||||
height: 0.5,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
@@ -15,7 +16,9 @@ class AppBarBackButton extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return AppBarButton(
|
||||
onTap: onTap ?? () => Navigator.pop(context),
|
||||
child: const Icon(Icons.arrow_back_ios_new),
|
||||
child: const FlowySvg(
|
||||
FlowySvgs.m_app_bar_back_s,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -24,30 +27,43 @@ class AppBarCloseButton extends StatelessWidget {
|
||||
const AppBarCloseButton({
|
||||
super.key,
|
||||
this.onTap,
|
||||
this.margin = const EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
vertical: 12.0,
|
||||
),
|
||||
});
|
||||
|
||||
final VoidCallback? onTap;
|
||||
final EdgeInsets margin;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FlowyButton(
|
||||
useIntrinsicWidth: true,
|
||||
text: const Icon(
|
||||
Icons.close,
|
||||
),
|
||||
margin: margin,
|
||||
return AppBarButton(
|
||||
onTap: onTap ?? () => Navigator.pop(context),
|
||||
child: const FlowySvg(
|
||||
FlowySvgs.m_app_bar_close_s,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AppBarCancelButton extends StatelessWidget {
|
||||
const AppBarCancelButton({
|
||||
super.key,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
final VoidCallback? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppBarButton(
|
||||
onTap: onTap ?? () => Navigator.pop(context),
|
||||
child: FlowyText(
|
||||
LocaleKeys.button_cancel.tr(),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AppBarDoneButton extends StatelessWidget {
|
||||
const AppBarDoneButton({
|
||||
super.key,
|
||||
required this.onTap,
|
||||
});
|
||||
@@ -57,9 +73,46 @@ class AppBarCancelButton extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppBarButton(
|
||||
isActionButton: true,
|
||||
onTap: onTap,
|
||||
child: FlowyText(
|
||||
LocaleKeys.button_cancel.tr(),
|
||||
LocaleKeys.button_Done.tr(),
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
fontWeight: FontWeight.w500,
|
||||
textAlign: TextAlign.right,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AppBarFilledDoneButton extends StatelessWidget {
|
||||
const AppBarFilledDoneButton({super.key, required this.onTap});
|
||||
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(8, 4, 8, 8),
|
||||
child: TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 5),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
elevation: 0,
|
||||
visualDensity: VisualDensity.compact,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
enableFeedback: true,
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
),
|
||||
onPressed: onTap,
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.button_done.tr(),
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -76,11 +129,9 @@ class AppBarMoreButton extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppBarButton(
|
||||
isActionButton: true,
|
||||
onTap: () => onTap(context),
|
||||
child: const Icon(
|
||||
// replace with flowy icon
|
||||
Icons.more_horiz_sharp,
|
||||
),
|
||||
child: const FlowySvg(FlowySvgs.three_dots_s),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -88,28 +139,27 @@ class AppBarMoreButton extends StatelessWidget {
|
||||
class AppBarButton extends StatelessWidget {
|
||||
const AppBarButton({
|
||||
super.key,
|
||||
this.extent = 16.0,
|
||||
this.isActionButton = false,
|
||||
required this.onTap,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
// used to extend the hit area of the more button
|
||||
final double extent;
|
||||
|
||||
final VoidCallback onTap;
|
||||
final Widget child;
|
||||
final bool isActionButton;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
enableFeedback: true,
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
splashColor: Colors.transparent,
|
||||
focusColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(extent),
|
||||
padding: EdgeInsets.only(
|
||||
top: 12.0,
|
||||
bottom: 12.0,
|
||||
left: 12.0,
|
||||
right: isActionButton ? 12.0 : 8.0,
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class FlowySearchTextField extends StatelessWidget {
|
||||
const FlowySearchTextField({
|
||||
super.key,
|
||||
this.hintText,
|
||||
this.controller,
|
||||
this.onChanged,
|
||||
this.onSubmitted,
|
||||
});
|
||||
|
||||
final String? hintText;
|
||||
final TextEditingController? controller;
|
||||
final ValueChanged<String>? onChanged;
|
||||
final ValueChanged<String>? onSubmitted;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 44.0,
|
||||
child: CupertinoSearchTextField(
|
||||
controller: controller,
|
||||
onChanged: onChanged,
|
||||
onSubmitted: onSubmitted,
|
||||
placeholder: hintText,
|
||||
prefixIcon: const FlowySvg(FlowySvgs.m_search_m),
|
||||
prefixInsets: const EdgeInsets.only(left: 16.0, right: 2.0),
|
||||
suffixIcon: const Icon(Icons.close),
|
||||
suffixInsets: const EdgeInsets.only(right: 16.0),
|
||||
placeholderStyle: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||
color: Theme.of(context).hintColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/app_bar.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/flowy_mobile_state_container.dart';
|
||||
import 'package:appflowy/plugins/base/emoji/emoji_text.dart';
|
||||
import 'package:appflowy/plugins/document/document_page.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
@@ -21,14 +24,16 @@ class MobileViewPage extends StatefulWidget {
|
||||
const MobileViewPage({
|
||||
super.key,
|
||||
required this.id,
|
||||
this.title,
|
||||
required this.viewLayout,
|
||||
this.title,
|
||||
this.arguments,
|
||||
});
|
||||
|
||||
/// view id
|
||||
final String id;
|
||||
final String? title;
|
||||
final ViewLayoutPB viewLayout;
|
||||
final String? title;
|
||||
final Map<String, dynamic>? arguments;
|
||||
|
||||
@override
|
||||
State<MobileViewPage> createState() => _MobileViewPageState();
|
||||
@@ -40,7 +45,6 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
future = ViewBackendService.getView(widget.id);
|
||||
}
|
||||
|
||||
@@ -67,7 +71,10 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
||||
body = state.data!.fold((view) {
|
||||
viewPB = view;
|
||||
actions.add(_buildAppBarMoreButton(view));
|
||||
return view.plugin().widgetBuilder.buildWidget(shrinkWrap: false);
|
||||
return view
|
||||
.plugin(arguments: widget.arguments ?? const {})
|
||||
.widgetBuilder
|
||||
.buildWidget(shrinkWrap: false);
|
||||
}, (error) {
|
||||
return FlowyMobileStateContainer.error(
|
||||
emoji: '😔',
|
||||
@@ -89,6 +96,10 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
||||
create: (_) =>
|
||||
ViewBloc(view: viewPB!)..add(const ViewEvent.initial()),
|
||||
),
|
||||
BlocProvider.value(
|
||||
value: getIt<ReminderBloc>()
|
||||
..add(const ReminderEvent.started()),
|
||||
),
|
||||
],
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
@@ -106,15 +117,12 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
||||
|
||||
Widget _buildApp(ViewPB? view, List<Widget> actions, Widget child) {
|
||||
final icon = view?.icon.value;
|
||||
final elevation = (view?.layout.isDatabaseView ?? false) ? 0.0 : null;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
titleSpacing: 0,
|
||||
elevation: elevation,
|
||||
appBar: FlowyAppBar(
|
||||
title: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (icon != null)
|
||||
if (icon != null && icon.isNotEmpty)
|
||||
EmojiText(
|
||||
emoji: '$icon ',
|
||||
fontSize: 22.0,
|
||||
@@ -128,12 +136,9 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
||||
),
|
||||
],
|
||||
),
|
||||
leading: const AppBarBackButton(),
|
||||
actions: actions,
|
||||
),
|
||||
body: SafeArea(
|
||||
child: child,
|
||||
),
|
||||
body: SafeArea(child: child),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -142,6 +147,9 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
||||
onTap: (context) {
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
showDragHandle: true,
|
||||
showDivider: false,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
builder: (_) => _buildViewPageBottomSheet(context),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/select_option_cell/extension.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell_editor/extension.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/select_option_entities.pb.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -34,6 +34,7 @@ class OptionColorList extends StatelessWidget {
|
||||
color: color,
|
||||
borderRadius: Corners.s12Border,
|
||||
border: Border.all(
|
||||
width: isSelected ? 2.0 : 1.0,
|
||||
color: isSelected
|
||||
? const Color(0xff00C6F1)
|
||||
: Theme.of(context).dividerColor,
|
||||
@@ -42,7 +43,7 @@ class OptionColorList extends StatelessWidget {
|
||||
alignment: Alignment.center,
|
||||
child: isSelected
|
||||
? const FlowySvg(
|
||||
FlowySvgs.blue_check_s,
|
||||
FlowySvgs.m_blue_check_s,
|
||||
size: Size.square(28.0),
|
||||
blendMode: null,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart' hide WidgetBuilder;
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TypeOptionMenuItemValue<T> {
|
||||
const TypeOptionMenuItemValue({
|
||||
required this.value,
|
||||
required this.icon,
|
||||
required this.text,
|
||||
required this.backgroundColor,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final T value;
|
||||
final FlowySvgData icon;
|
||||
final String text;
|
||||
final Color backgroundColor;
|
||||
final void Function(BuildContext context, T value) onTap;
|
||||
}
|
||||
|
||||
class TypeOptionMenu<T> extends StatelessWidget {
|
||||
const TypeOptionMenu({
|
||||
super.key,
|
||||
required this.values,
|
||||
this.width = 94,
|
||||
this.iconWidth = 72,
|
||||
this.scaleFactor = 1.0,
|
||||
this.maxAxisSpacing = 18,
|
||||
this.crossAxisCount = 3,
|
||||
});
|
||||
|
||||
final List<TypeOptionMenuItemValue<T>> values;
|
||||
|
||||
final double iconWidth;
|
||||
final double width;
|
||||
final double scaleFactor;
|
||||
final double maxAxisSpacing;
|
||||
final int crossAxisCount;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _GridView(
|
||||
crossAxisCount: crossAxisCount,
|
||||
mainAxisSpacing: maxAxisSpacing * scaleFactor,
|
||||
itemWidth: width * scaleFactor,
|
||||
children: values
|
||||
.map(
|
||||
(value) => _TypeOptionMenuItem<T>(
|
||||
value: value,
|
||||
width: width,
|
||||
iconWidth: iconWidth,
|
||||
scaleFactor: scaleFactor,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TypeOptionMenuItem<T> extends StatelessWidget {
|
||||
const _TypeOptionMenuItem({
|
||||
required this.value,
|
||||
this.width = 94,
|
||||
this.iconWidth = 72,
|
||||
this.scaleFactor = 1.0,
|
||||
});
|
||||
|
||||
final TypeOptionMenuItemValue<T> value;
|
||||
final double iconWidth;
|
||||
final double width;
|
||||
final double scaleFactor;
|
||||
|
||||
double get scaledIconWidth => iconWidth * scaleFactor;
|
||||
double get scaledWidth => width * scaleFactor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () => value.onTap(context, value.value),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: scaledIconWidth,
|
||||
width: scaledIconWidth,
|
||||
decoration: ShapeDecoration(
|
||||
color: value.backgroundColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(24 * scaleFactor),
|
||||
),
|
||||
),
|
||||
padding: EdgeInsets.all(21 * scaleFactor),
|
||||
child: FlowySvg(
|
||||
value.icon,
|
||||
),
|
||||
),
|
||||
const VSpace(6),
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: scaledWidth,
|
||||
),
|
||||
child: FlowyText(
|
||||
value.text,
|
||||
fontSize: 14.0,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _GridView extends StatelessWidget {
|
||||
const _GridView({
|
||||
required this.children,
|
||||
required this.crossAxisCount,
|
||||
required this.mainAxisSpacing,
|
||||
required this.itemWidth,
|
||||
});
|
||||
|
||||
final List<Widget> children;
|
||||
final int crossAxisCount;
|
||||
final double mainAxisSpacing;
|
||||
final double itemWidth;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
for (var i = 0; i < children.length; i += crossAxisCount)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(bottom: mainAxisSpacing),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
for (var j = 0; j < crossAxisCount; j++)
|
||||
i + j < children.length
|
||||
? SizedBox(
|
||||
width: itemWidth,
|
||||
child: children[i + j],
|
||||
)
|
||||
: HSpace(itemWidth),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ export 'bottom_sheet_drag_handler.dart';
|
||||
export 'bottom_sheet_rename_widget.dart';
|
||||
export 'bottom_sheet_view_item.dart';
|
||||
export 'bottom_sheet_view_item_body.dart';
|
||||
export 'bottom_sheet_view_item_header.dart';
|
||||
export 'bottom_sheet_view_page.dart';
|
||||
export 'default_mobile_action_pane.dart';
|
||||
export 'show_mobile_bottom_sheet.dart';
|
||||
+35
-42
@@ -1,9 +1,8 @@
|
||||
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_action_widget.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AddNewPageWidgetBottomSheet extends StatelessWidget {
|
||||
@@ -20,47 +19,41 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
// new document, new grid
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: BottomSheetActionWidget(
|
||||
svg: FlowySvgs.document_s,
|
||||
text: LocaleKeys.document_menuName.tr(),
|
||||
onTap: () => onAction(ViewLayoutPB.Document),
|
||||
),
|
||||
),
|
||||
const HSpace(8),
|
||||
Expanded(
|
||||
child: BottomSheetActionWidget(
|
||||
svg: FlowySvgs.grid_s,
|
||||
text: LocaleKeys.grid_menuName.tr(),
|
||||
onTap: () => onAction(ViewLayoutPB.Grid),
|
||||
),
|
||||
),
|
||||
],
|
||||
FlowyOptionTile.text(
|
||||
text: LocaleKeys.document_menuName.tr(),
|
||||
leftIcon: const FlowySvg(
|
||||
FlowySvgs.document_s,
|
||||
size: Size.square(20),
|
||||
),
|
||||
showTopBorder: false,
|
||||
onTap: () => onAction(ViewLayoutPB.Document),
|
||||
),
|
||||
const VSpace(8),
|
||||
|
||||
// new board, new calendar
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: BottomSheetActionWidget(
|
||||
svg: FlowySvgs.board_s,
|
||||
text: LocaleKeys.board_menuName.tr(),
|
||||
onTap: () => onAction(ViewLayoutPB.Board),
|
||||
),
|
||||
),
|
||||
const HSpace(8),
|
||||
Expanded(
|
||||
child: BottomSheetActionWidget(
|
||||
svg: FlowySvgs.date_s,
|
||||
text: LocaleKeys.calendar_menuName.tr(),
|
||||
onTap: () => onAction(ViewLayoutPB.Calendar),
|
||||
),
|
||||
),
|
||||
],
|
||||
FlowyOptionTile.text(
|
||||
text: LocaleKeys.grid_menuName.tr(),
|
||||
leftIcon: const FlowySvg(
|
||||
FlowySvgs.grid_s,
|
||||
size: Size.square(20),
|
||||
),
|
||||
showTopBorder: false,
|
||||
onTap: () => onAction(ViewLayoutPB.Grid),
|
||||
),
|
||||
FlowyOptionTile.text(
|
||||
text: LocaleKeys.board_menuName.tr(),
|
||||
leftIcon: const FlowySvg(
|
||||
FlowySvgs.board_s,
|
||||
size: Size.square(20),
|
||||
),
|
||||
showTopBorder: false,
|
||||
onTap: () => onAction(ViewLayoutPB.Board),
|
||||
),
|
||||
FlowyOptionTile.text(
|
||||
text: LocaleKeys.calendar_menuName.tr(),
|
||||
leftIcon: const FlowySvg(
|
||||
FlowySvgs.date_s,
|
||||
size: Size.square(20),
|
||||
),
|
||||
showTopBorder: false,
|
||||
onTap: () => onAction(ViewLayoutPB.Calendar),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
+33
-41
@@ -1,8 +1,7 @@
|
||||
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_action_widget.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum BlockActionBottomSheetType {
|
||||
@@ -28,50 +27,43 @@ class BlockActionBottomSheet extends StatelessWidget {
|
||||
return Column(
|
||||
children: [
|
||||
// insert above, insert below
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: BottomSheetActionWidget(
|
||||
svg: FlowySvgs.arrow_up_s,
|
||||
text: LocaleKeys.button_insertAbove.tr(),
|
||||
onTap: () => onAction(BlockActionBottomSheetType.insertAbove),
|
||||
),
|
||||
),
|
||||
const HSpace(8),
|
||||
Expanded(
|
||||
child: BottomSheetActionWidget(
|
||||
svg: FlowySvgs.arrow_down_s,
|
||||
text: LocaleKeys.button_insertBelow.tr(),
|
||||
onTap: () => onAction(BlockActionBottomSheetType.insertBelow),
|
||||
),
|
||||
),
|
||||
],
|
||||
FlowyOptionTile.text(
|
||||
text: LocaleKeys.button_insertAbove.tr(),
|
||||
leftIcon: const FlowySvg(
|
||||
FlowySvgs.arrow_up_s,
|
||||
size: Size.square(20),
|
||||
),
|
||||
onTap: () => onAction(BlockActionBottomSheetType.insertAbove),
|
||||
),
|
||||
FlowyOptionTile.text(
|
||||
showTopBorder: false,
|
||||
text: LocaleKeys.button_insertBelow.tr(),
|
||||
leftIcon: const FlowySvg(
|
||||
FlowySvgs.arrow_down_s,
|
||||
size: Size.square(20),
|
||||
),
|
||||
onTap: () => onAction(BlockActionBottomSheetType.insertBelow),
|
||||
),
|
||||
const VSpace(8),
|
||||
|
||||
// duplicate, delete
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: BottomSheetActionWidget(
|
||||
svg: FlowySvgs.m_duplicate_m,
|
||||
text: LocaleKeys.button_duplicate.tr(),
|
||||
onTap: () => onAction(BlockActionBottomSheetType.duplicate),
|
||||
),
|
||||
),
|
||||
const HSpace(8),
|
||||
Expanded(
|
||||
child: BottomSheetActionWidget(
|
||||
svg: FlowySvgs.m_delete_m,
|
||||
text: LocaleKeys.button_delete.tr(),
|
||||
onTap: () => onAction(BlockActionBottomSheetType.delete),
|
||||
),
|
||||
),
|
||||
],
|
||||
FlowyOptionTile.text(
|
||||
showTopBorder: false,
|
||||
text: LocaleKeys.button_duplicate.tr(),
|
||||
leftIcon: const FlowySvg(FlowySvgs.m_field_copy_s),
|
||||
onTap: () => onAction(BlockActionBottomSheetType.duplicate),
|
||||
),
|
||||
const VSpace(8),
|
||||
|
||||
...extendActionWidgets,
|
||||
|
||||
FlowyOptionTile.text(
|
||||
showTopBorder: false,
|
||||
text: LocaleKeys.button_delete.tr(),
|
||||
leftIcon: FlowySvg(
|
||||
FlowySvgs.m_delete_s,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
textColor: Theme.of(context).colorScheme.error,
|
||||
onTap: () => onAction(BlockActionBottomSheetType.delete),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
-1
@@ -44,7 +44,6 @@ class _MobileBottomSheetEditLinkWidgetState
|
||||
void dispose() {
|
||||
textController.dispose();
|
||||
hrefController.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
-2
@@ -27,14 +27,12 @@ class BottomSheetHeader extends StatelessWidget {
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: AppBarCloseButton(
|
||||
margin: EdgeInsets.zero,
|
||||
onTap: onClose,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (title != null)
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: FlowyText.medium(
|
||||
title!,
|
||||
fontSize: 16,
|
||||
|
||||
+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();
|
||||
}
|
||||
|
||||
|
||||
+5
-37
@@ -2,10 +2,8 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart' hide WidgetBuilder;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
enum MobileBottomSheetType {
|
||||
view,
|
||||
@@ -39,36 +37,6 @@ class _MobileViewItemBottomSheetState extends State<MobileViewItemBottomSheet> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// header
|
||||
_buildHeader(),
|
||||
const VSpace(16),
|
||||
// body
|
||||
_buildBody(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
switch (type) {
|
||||
case MobileBottomSheetType.view:
|
||||
case MobileBottomSheetType.rename:
|
||||
// header
|
||||
return MobileViewItemBottomSheetHeader(
|
||||
showBackButton: type != MobileBottomSheetType.view,
|
||||
view: widget.view,
|
||||
onBack: () {
|
||||
setState(() {
|
||||
type = MobileBottomSheetType.view;
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildBody() {
|
||||
switch (type) {
|
||||
case MobileBottomSheetType.view:
|
||||
return MobileViewItemBottomSheetBody(
|
||||
@@ -81,21 +49,21 @@ class _MobileViewItemBottomSheetState extends State<MobileViewItemBottomSheet> {
|
||||
});
|
||||
break;
|
||||
case MobileViewItemBottomSheetBodyAction.duplicate:
|
||||
context.pop();
|
||||
Navigator.pop(context);
|
||||
context.read<ViewBloc>().add(const ViewEvent.duplicate());
|
||||
break;
|
||||
case MobileViewItemBottomSheetBodyAction.share:
|
||||
// unimplemented
|
||||
context.pop();
|
||||
Navigator.pop(context);
|
||||
break;
|
||||
case MobileViewItemBottomSheetBodyAction.delete:
|
||||
context.pop();
|
||||
Navigator.pop(context);
|
||||
context.read<ViewBloc>().add(const ViewEvent.delete());
|
||||
|
||||
break;
|
||||
case MobileViewItemBottomSheetBodyAction.addToFavorites:
|
||||
case MobileViewItemBottomSheetBodyAction.removeFromFavorites:
|
||||
context.pop();
|
||||
Navigator.pop(context);
|
||||
context
|
||||
.read<FavoriteBloc>()
|
||||
.add(FavoriteEvent.toggle(widget.view));
|
||||
@@ -110,7 +78,7 @@ class _MobileViewItemBottomSheetState extends State<MobileViewItemBottomSheet> {
|
||||
if (name != widget.view.name) {
|
||||
context.read<ViewBloc>().add(ViewEvent.rename(name));
|
||||
}
|
||||
context.pop();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
+34
-63
@@ -1,8 +1,8 @@
|
||||
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_action_widget.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/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/widget/separated_flex.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum MobileViewItemBottomSheetBodyAction {
|
||||
@@ -26,79 +26,50 @@ 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: [
|
||||
// rename, duplicate
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Expanded(
|
||||
child: BottomSheetActionWidget(
|
||||
svg: FlowySvgs.m_rename_m,
|
||||
text: LocaleKeys.button_rename.tr(),
|
||||
onTap: () => onAction(
|
||||
MobileViewItemBottomSheetBodyAction.rename,
|
||||
),
|
||||
),
|
||||
),
|
||||
const HSpace(8),
|
||||
Expanded(
|
||||
child: BottomSheetActionWidget(
|
||||
svg: FlowySvgs.m_duplicate_m,
|
||||
text: LocaleKeys.button_duplicate.tr(),
|
||||
onTap: () => onAction(
|
||||
MobileViewItemBottomSheetBodyAction.duplicate,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.button_rename.tr(),
|
||||
icon: FlowySvgs.m_rename_s,
|
||||
onTap: () => onAction(
|
||||
MobileViewItemBottomSheetBodyAction.rename,
|
||||
),
|
||||
),
|
||||
const VSpace(8),
|
||||
|
||||
// share, delete
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Expanded(
|
||||
child: BottomSheetActionWidget(
|
||||
svg: FlowySvgs.m_share_m,
|
||||
text: LocaleKeys.button_share.tr(),
|
||||
onTap: () => onAction(
|
||||
MobileViewItemBottomSheetBodyAction.share,
|
||||
),
|
||||
),
|
||||
),
|
||||
const HSpace(8),
|
||||
Expanded(
|
||||
child: BottomSheetActionWidget(
|
||||
svg: FlowySvgs.m_delete_m,
|
||||
text: LocaleKeys.button_delete.tr(),
|
||||
onTap: () => onAction(
|
||||
MobileViewItemBottomSheetBodyAction.delete,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const VSpace(8),
|
||||
|
||||
// remove from favorites/add to favorites
|
||||
BottomSheetActionWidget(
|
||||
svg: isFavorite
|
||||
? FlowySvgs.m_favorite_selected_lg
|
||||
: FlowySvgs.m_favorite_unselected_lg,
|
||||
//TODO(yijing): switch to theme color
|
||||
iconColor: isFavorite ? Colors.yellow : null,
|
||||
MobileQuickActionButton(
|
||||
text: isFavorite
|
||||
? LocaleKeys.button_removeFromFavorites.tr()
|
||||
: LocaleKeys.button_addToFavorites.tr(),
|
||||
icon: isFavorite
|
||||
? FlowySvgs.m_favorite_selected_lg
|
||||
: FlowySvgs.m_favorite_unselected_lg,
|
||||
iconColor: isFavorite ? Colors.yellow : null,
|
||||
onTap: () => onAction(
|
||||
isFavorite
|
||||
? MobileViewItemBottomSheetBodyAction.removeFromFavorites
|
||||
: MobileViewItemBottomSheetBodyAction.addToFavorites,
|
||||
),
|
||||
),
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.button_duplicate.tr(),
|
||||
icon: FlowySvgs.m_duplicate_s,
|
||||
onTap: () => onAction(
|
||||
MobileViewItemBottomSheetBodyAction.duplicate,
|
||||
),
|
||||
),
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.button_delete.tr(),
|
||||
textColor: Theme.of(context).colorScheme.error,
|
||||
icon: FlowySvgs.m_delete_s,
|
||||
iconColor: Theme.of(context).colorScheme.error,
|
||||
onTap: () => onAction(
|
||||
MobileViewItemBottomSheetBodyAction.delete,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
-59
@@ -1,59 +0,0 @@
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class MobileViewItemBottomSheetHeader extends StatelessWidget {
|
||||
const MobileViewItemBottomSheetHeader({
|
||||
super.key,
|
||||
required this.view,
|
||||
required this.showBackButton,
|
||||
required this.onBack,
|
||||
});
|
||||
|
||||
final ViewPB view;
|
||||
final bool showBackButton;
|
||||
final VoidCallback onBack;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
// back button,
|
||||
showBackButton
|
||||
? InkWell(
|
||||
onTap: onBack,
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Icon(
|
||||
Icons.arrow_back_ios_new_rounded,
|
||||
size: 24.0,
|
||||
),
|
||||
),
|
||||
)
|
||||
: FlowyButton(
|
||||
useIntrinsicWidth: true,
|
||||
text: const Icon(
|
||||
Icons.close,
|
||||
),
|
||||
margin: EdgeInsets.zero,
|
||||
onTap: () {
|
||||
context.pop();
|
||||
},
|
||||
),
|
||||
// title
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * 0.6,
|
||||
),
|
||||
child: FlowyText.medium(
|
||||
view.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const HSpace(24.0),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
+33
-101
@@ -1,6 +1,7 @@
|
||||
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_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
@@ -43,36 +44,6 @@ class _ViewPageBottomSheetState extends State<ViewPageBottomSheet> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// header
|
||||
_buildHeader(),
|
||||
const VSpace(16),
|
||||
// body
|
||||
_buildBody(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
switch (type) {
|
||||
case MobileBottomSheetType.view:
|
||||
case MobileBottomSheetType.rename:
|
||||
// header
|
||||
return MobileViewItemBottomSheetHeader(
|
||||
showBackButton: type != MobileBottomSheetType.view,
|
||||
view: widget.view,
|
||||
onBack: () {
|
||||
setState(() {
|
||||
type = MobileBottomSheetType.view;
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildBody() {
|
||||
switch (type) {
|
||||
case MobileBottomSheetType.view:
|
||||
return MobileViewBottomSheetBody(
|
||||
@@ -114,89 +85,50 @@ 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: [
|
||||
// rename, duplicate
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Expanded(
|
||||
child: BottomSheetActionWidget(
|
||||
svg: FlowySvgs.m_rename_m,
|
||||
text: LocaleKeys.button_rename.tr(),
|
||||
onTap: () => onAction(
|
||||
MobileViewBottomSheetBodyAction.rename,
|
||||
),
|
||||
),
|
||||
),
|
||||
const HSpace(8),
|
||||
Expanded(
|
||||
child: BottomSheetActionWidget(
|
||||
svg: FlowySvgs.m_duplicate_m,
|
||||
text: LocaleKeys.button_duplicate.tr(),
|
||||
onTap: () => onAction(
|
||||
MobileViewBottomSheetBodyAction.duplicate,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.button_rename.tr(),
|
||||
icon: FlowySvgs.m_rename_s,
|
||||
onTap: () => onAction(
|
||||
MobileViewBottomSheetBodyAction.rename,
|
||||
),
|
||||
),
|
||||
const VSpace(8),
|
||||
|
||||
// share, delete
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Expanded(
|
||||
child: BottomSheetActionWidget(
|
||||
svg: FlowySvgs.m_share_m,
|
||||
text: LocaleKeys.button_share.tr(),
|
||||
onTap: () => onAction(
|
||||
MobileViewBottomSheetBodyAction.share,
|
||||
),
|
||||
),
|
||||
),
|
||||
const HSpace(8),
|
||||
Expanded(
|
||||
child: BottomSheetActionWidget(
|
||||
svg: FlowySvgs.m_delete_m,
|
||||
text: LocaleKeys.button_delete.tr(),
|
||||
onTap: () => onAction(
|
||||
MobileViewBottomSheetBodyAction.delete,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const VSpace(8),
|
||||
|
||||
// favorites
|
||||
BottomSheetActionWidget(
|
||||
svg: isFavorite
|
||||
? FlowySvgs.m_favorite_selected_lg
|
||||
: FlowySvgs.m_favorite_unselected_lg,
|
||||
//TODO(yijing): switch to theme color
|
||||
iconColor: isFavorite ? Colors.yellow : null,
|
||||
MobileQuickActionButton(
|
||||
text: isFavorite
|
||||
? LocaleKeys.button_removeFromFavorites.tr()
|
||||
: LocaleKeys.button_addToFavorites.tr(),
|
||||
icon: isFavorite
|
||||
? FlowySvgs.m_favorite_selected_lg
|
||||
: FlowySvgs.m_favorite_unselected_lg,
|
||||
iconColor: isFavorite ? Colors.yellow : null,
|
||||
onTap: () => onAction(
|
||||
isFavorite
|
||||
? MobileViewBottomSheetBodyAction.removeFromFavorites
|
||||
: MobileViewBottomSheetBodyAction.addToFavorites,
|
||||
),
|
||||
),
|
||||
|
||||
// Help Center
|
||||
// const VSpace(8),
|
||||
// BottomSheetActionWidget(
|
||||
// svg: FlowySvgs.m_help_center_m,
|
||||
// text: LocaleKeys.button_helpCenter.tr(),
|
||||
// onTap: () => onAction(
|
||||
// MobileViewBottomSheetBodyAction.helpCenter,
|
||||
// ),
|
||||
// ),
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.button_duplicate.tr(),
|
||||
icon: FlowySvgs.m_duplicate_s,
|
||||
onTap: () => onAction(
|
||||
MobileViewBottomSheetBodyAction.duplicate,
|
||||
),
|
||||
),
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.button_delete.tr(),
|
||||
textColor: Theme.of(context).colorScheme.error,
|
||||
icon: FlowySvgs.m_delete_s,
|
||||
iconColor: Theme.of(context).colorScheme.error,
|
||||
onTap: () => onAction(
|
||||
MobileViewBottomSheetBodyAction.delete,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
+4
@@ -52,6 +52,10 @@ enum MobilePaneActionType {
|
||||
final favoriteBloc = context.read<FavoriteBloc>();
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
showDragHandle: true,
|
||||
showDivider: false,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
useRootNavigator: true,
|
||||
builder: (context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
|
||||
+157
-51
@@ -7,25 +7,48 @@ import 'package:flutter/material.dart';
|
||||
Future<T?> showMobileBottomSheet<T>(
|
||||
BuildContext context, {
|
||||
required WidgetBuilder builder,
|
||||
ShapeBorder? shape,
|
||||
bool useSafeArea = true,
|
||||
bool isDragEnabled = true,
|
||||
bool resizeToAvoidBottomInset = true,
|
||||
EdgeInsets padding = const EdgeInsets.fromLTRB(16, 16, 16, 32),
|
||||
bool showDragHandle = false,
|
||||
bool showHeader = false,
|
||||
// this field is only used if showHeader is true
|
||||
bool showBackButton = false,
|
||||
bool showCloseButton = false,
|
||||
String title = '', // only works if showHeader is true
|
||||
Color? backgroundColor,
|
||||
// this field is only used if showHeader is true
|
||||
String title = '',
|
||||
bool resizeToAvoidBottomInset = true,
|
||||
bool isScrollControlled = true,
|
||||
BoxConstraints? constraints,
|
||||
bool showDivider = true,
|
||||
bool useRootNavigator = false,
|
||||
ShapeBorder? shape,
|
||||
// the padding of the content, the padding of the header area is fixed
|
||||
EdgeInsets padding = EdgeInsets.zero,
|
||||
Color? backgroundColor,
|
||||
BoxConstraints? constraints,
|
||||
Color? barrierColor,
|
||||
double? elevation,
|
||||
bool showDoneButton = false,
|
||||
bool enableDraggableScrollable = false,
|
||||
// only used when enableDraggableScrollable is true
|
||||
double minChildSize = 0.5,
|
||||
double maxChildSize = 0.8,
|
||||
double initialChildSize = 0.51,
|
||||
}) async {
|
||||
assert(() {
|
||||
if (showCloseButton || title.isNotEmpty) assert(showHeader);
|
||||
return true;
|
||||
}());
|
||||
assert(
|
||||
showHeader ||
|
||||
title.isEmpty && !showCloseButton && !showBackButton && !showDoneButton,
|
||||
);
|
||||
assert(!(showCloseButton && showBackButton));
|
||||
|
||||
shape ??= const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Corners.s12Radius,
|
||||
),
|
||||
);
|
||||
|
||||
backgroundColor ??= Theme.of(context).brightness == Brightness.light
|
||||
? const Color(0xFFF7F8FB)
|
||||
: const Color(0xFF626364);
|
||||
|
||||
return showModalBottomSheet<T>(
|
||||
context: context,
|
||||
@@ -33,81 +56,164 @@ Future<T?> showMobileBottomSheet<T>(
|
||||
enableDrag: isDragEnabled,
|
||||
useSafeArea: true,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
backgroundColor: backgroundColor,
|
||||
constraints: constraints,
|
||||
barrierColor: barrierColor,
|
||||
elevation: elevation,
|
||||
shape: shape ??
|
||||
const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Corners.s12Radius,
|
||||
),
|
||||
),
|
||||
backgroundColor: backgroundColor,
|
||||
shape: shape,
|
||||
useRootNavigator: useRootNavigator,
|
||||
builder: (context) {
|
||||
final List<Widget> children = [];
|
||||
|
||||
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) {
|
||||
return child;
|
||||
}
|
||||
|
||||
// ----- header area -----
|
||||
if (showDragHandle) {
|
||||
children.addAll([
|
||||
children.add(
|
||||
const DragHandler(),
|
||||
]);
|
||||
);
|
||||
}
|
||||
|
||||
if (showHeader) {
|
||||
children.addAll([
|
||||
VSpace(padding.top),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
showCloseButton
|
||||
? Padding(
|
||||
padding: EdgeInsets.only(left: padding.left),
|
||||
child: const AppBarCloseButton(
|
||||
margin: EdgeInsets.zero,
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
FlowyText(
|
||||
title,
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
showCloseButton
|
||||
? HSpace(padding.right + 24)
|
||||
: const SizedBox.shrink(),
|
||||
],
|
||||
children.add(
|
||||
_Header(
|
||||
showCloseButton: showCloseButton,
|
||||
showBackButton: showBackButton,
|
||||
showDoneButton: showDoneButton,
|
||||
title: title,
|
||||
),
|
||||
const VSpace(4),
|
||||
if (showDivider) const Divider(),
|
||||
]);
|
||||
);
|
||||
|
||||
if (showDivider) {
|
||||
children.add(
|
||||
const Divider(height: 0.5, thickness: 0.5),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final child = builder(context);
|
||||
// ----- header area -----
|
||||
|
||||
if (enableDraggableScrollable) {
|
||||
return DraggableScrollableSheet(
|
||||
expand: false,
|
||||
snap: true,
|
||||
initialChildSize: initialChildSize,
|
||||
minChildSize: minChildSize,
|
||||
maxChildSize: maxChildSize,
|
||||
builder: (context, scrollController) {
|
||||
return Column(
|
||||
children: [
|
||||
...children,
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// ----- content area -----
|
||||
if (resizeToAvoidBottomInset) {
|
||||
children.add(
|
||||
AnimatedPadding(
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: showHeader ? 0 : padding.top,
|
||||
top: padding.top,
|
||||
left: padding.left,
|
||||
right: padding.right,
|
||||
bottom: padding.bottom + MediaQuery.of(context).viewInsets.bottom,
|
||||
),
|
||||
duration: Duration.zero,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
children.add(child);
|
||||
}
|
||||
// ----- content area -----
|
||||
|
||||
if (children.length == 1) {
|
||||
return children.first;
|
||||
}
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: children,
|
||||
// add default padding
|
||||
children.add(
|
||||
VSpace(MediaQuery.of(context).padding.bottom == 0 ? 28.0 : 16.0),
|
||||
);
|
||||
|
||||
return useSafeArea
|
||||
? SafeArea(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: children,
|
||||
),
|
||||
)
|
||||
: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: children,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class _Header extends StatelessWidget {
|
||||
const _Header({
|
||||
required this.showBackButton,
|
||||
required this.showCloseButton,
|
||||
required this.title,
|
||||
required this.showDoneButton,
|
||||
});
|
||||
|
||||
final bool showBackButton;
|
||||
final bool showCloseButton;
|
||||
final String title;
|
||||
final bool showDoneButton;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4.0),
|
||||
child: SizedBox(
|
||||
height: 44.0, // the height of the header area is fixed
|
||||
child: Stack(
|
||||
children: [
|
||||
if (showBackButton)
|
||||
const Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: AppBarBackButton(),
|
||||
),
|
||||
if (showCloseButton)
|
||||
const Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: AppBarCloseButton(),
|
||||
),
|
||||
Align(
|
||||
child: FlowyText(
|
||||
title,
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
if (showDoneButton)
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: AppBarDoneButton(
|
||||
onTap: () => Navigator.pop(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+24
-36
@@ -6,8 +6,8 @@ import 'package:appflowy/mobile/presentation/database/card/card.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/plugins/database/widgets/card/card.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/card/card_cell_builder.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/card/cells/card_cell.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/card_cell_builder.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/card_cell_style_maps/mobile_board_card_cell_style.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:appflowy_board/appflowy_board.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@@ -26,28 +26,22 @@ class MobileBoardContent extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MobileBoardContentState extends State<MobileBoardContent> {
|
||||
final renderHook = RowCardRenderHook<String>();
|
||||
late final ScrollController scrollController;
|
||||
late final AppFlowyBoardScrollController scrollManager;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
//mobile may not need this
|
||||
//scroll to bottom when add a new card
|
||||
// mobile may not need this
|
||||
// scroll to bottom when add a new card
|
||||
scrollManager = AppFlowyBoardScrollController();
|
||||
scrollController = ScrollController();
|
||||
renderHook.addSelectOptionHook((options, groupId, _) {
|
||||
// The cell should hide if the option id is equal to the groupId.
|
||||
final isInGroup =
|
||||
options.where((element) => element.id == groupId).isNotEmpty;
|
||||
}
|
||||
|
||||
if (isInGroup || options.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
@override
|
||||
void dispose() {
|
||||
scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -156,10 +150,10 @@ class _MobileBoardContentState extends State<MobileBoardContent> {
|
||||
|
||||
/// Return placeholder widget if the rowCache is null.
|
||||
if (rowCache == null) return SizedBox.shrink(key: ObjectKey(groupItem));
|
||||
final cellCache = rowCache.cellCache;
|
||||
final viewId = boardBloc.viewId;
|
||||
|
||||
final cellBuilder = CardCellBuilder<String>(cellCache);
|
||||
final cellBuilder =
|
||||
CardCellBuilder(databaseController: boardBloc.databaseController);
|
||||
final isEditing = boardBloc.state.isEditingRow &&
|
||||
boardBloc.state.editingRow?.row.id == groupItem.row.id;
|
||||
|
||||
@@ -169,15 +163,14 @@ class _MobileBoardContentState extends State<MobileBoardContent> {
|
||||
key: ValueKey(groupItemId),
|
||||
margin: cardMargin,
|
||||
decoration: _makeBoxDecoration(context),
|
||||
child: RowCard<String>(
|
||||
child: RowCard(
|
||||
fieldController: boardBloc.fieldController,
|
||||
rowMeta: rowMeta,
|
||||
viewId: viewId,
|
||||
rowCache: rowCache,
|
||||
cardData: groupData.group.groupId,
|
||||
groupingFieldId: groupItem.fieldInfo.id,
|
||||
isEditing: isEditing,
|
||||
cellBuilder: cellBuilder,
|
||||
renderHook: renderHook,
|
||||
openCard: (context) {
|
||||
context.push(
|
||||
MobileRowDetailPage.routeName,
|
||||
@@ -192,11 +185,9 @@ class _MobileBoardContentState extends State<MobileBoardContent> {
|
||||
.add(BoardEvent.startEditingRow(groupData.group, groupItem.row)),
|
||||
onEndEditing: () =>
|
||||
boardBloc.add(BoardEvent.endEditingRow(groupItem.row.id)),
|
||||
styleConfiguration: const RowCardStyleConfiguration(
|
||||
styleConfiguration: RowCardStyleConfiguration(
|
||||
cellStyleMap: mobileBoardCardCellStyleMap(context),
|
||||
showAccessory: false,
|
||||
hoverStyle: null,
|
||||
cardPadding: EdgeInsets.all(8),
|
||||
cellPadding: EdgeInsets.zero,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -214,18 +205,15 @@ class _MobileBoardContentState extends State<MobileBoardContent> {
|
||||
),
|
||||
)
|
||||
: null,
|
||||
boxShadow:
|
||||
// The shadow is only visible in light mode.
|
||||
themeMode == ThemeMode.light
|
||||
? [
|
||||
BoxShadow(
|
||||
color:
|
||||
Theme.of(context).colorScheme.outline.withOpacity(0.5),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
]
|
||||
: null,
|
||||
boxShadow: themeMode == ThemeMode.light
|
||||
? [
|
||||
BoxShadow(
|
||||
color: Theme.of(context).colorScheme.outline.withOpacity(0.5),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
]
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -3,10 +3,6 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MobileBoardScreen extends StatelessWidget {
|
||||
static const routeName = '/board';
|
||||
static const viewId = 'id';
|
||||
static const viewTitle = 'title';
|
||||
|
||||
const MobileBoardScreen({
|
||||
super.key,
|
||||
required this.id,
|
||||
@@ -17,6 +13,10 @@ class MobileBoardScreen extends StatelessWidget {
|
||||
final String id;
|
||||
final String? title;
|
||||
|
||||
static const routeName = '/board';
|
||||
static const viewId = 'id';
|
||||
static const viewTitle = 'title';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MobileViewPage(
|
||||
|
||||
-1
@@ -82,7 +82,6 @@ class _GroupCardHeaderState extends State<GroupCardHeader> {
|
||||
_controller.text,
|
||||
),
|
||||
),
|
||||
maxLines: 1,
|
||||
style: titleTextStyle,
|
||||
onTapOutside: (_) => context.read<BoardBloc>().add(
|
||||
// group header switch from TextField to Text
|
||||
|
||||
+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;
|
||||
|
||||
+102
-167
@@ -3,12 +3,9 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/database/card/card.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/plugins/database/application/database_controller.dart';
|
||||
import 'package:appflowy/plugins/database/application/field/field_info.dart';
|
||||
import 'package:appflowy/plugins/database/application/row/row_cache.dart';
|
||||
import 'package:appflowy/plugins/database/board/application/board_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/card/card_cell_builder.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/card/cells/card_cell.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/text_cell/text_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/card_cell_builder.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/card_cell_skeleton/text_card_cell.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@@ -107,7 +104,6 @@ class MobileHiddenGroupList extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bloc = context.read<BoardBloc>();
|
||||
return BlocBuilder<BoardBloc, BoardState>(
|
||||
builder: (_, state) => ReorderableListView.builder(
|
||||
itemCount: state.hiddenGroups.length,
|
||||
@@ -115,7 +111,10 @@ class MobileHiddenGroupList extends StatelessWidget {
|
||||
key: ValueKey(state.hiddenGroups[index].groupId),
|
||||
group: state.hiddenGroups[index],
|
||||
index: index,
|
||||
bloc: bloc,
|
||||
),
|
||||
proxyDecorator: (child, index, animation) => BlocProvider.value(
|
||||
value: context.read<BoardBloc>(),
|
||||
child: Material(color: Colors.transparent, child: child),
|
||||
),
|
||||
physics: const ClampingScrollPhysics(),
|
||||
onReorder: (oldIndex, newIndex) {
|
||||
@@ -124,7 +123,9 @@ class MobileHiddenGroupList extends StatelessWidget {
|
||||
}
|
||||
final fromGroupId = state.hiddenGroups[oldIndex].groupId;
|
||||
final toGroupId = state.hiddenGroups[newIndex].groupId;
|
||||
bloc.add(BoardEvent.reorderGroup(fromGroupId, toGroupId));
|
||||
context
|
||||
.read<BoardBloc>()
|
||||
.add(BoardEvent.reorderGroup(fromGroupId, toGroupId));
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -136,181 +137,115 @@ class MobileHiddenGroup extends StatelessWidget {
|
||||
super.key,
|
||||
required this.group,
|
||||
required this.index,
|
||||
required this.bloc,
|
||||
});
|
||||
|
||||
final GroupPB group;
|
||||
final BoardBloc bloc;
|
||||
final int index;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final databaseController = bloc.databaseController;
|
||||
final databaseController = context.read<BoardBloc>().databaseController;
|
||||
final primaryField = databaseController.fieldController.fieldInfos
|
||||
.firstWhereOrNull((element) => element.isPrimary)!;
|
||||
|
||||
return BlocProvider<BoardBloc>.value(
|
||||
value: bloc,
|
||||
child: BlocBuilder<BoardBloc, BoardState>(
|
||||
builder: (context, state) {
|
||||
final group = state.hiddenGroups.firstWhereOrNull(
|
||||
(g) => g.groupId == this.group.groupId,
|
||||
);
|
||||
if (group == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return BlocBuilder<BoardBloc, BoardState>(
|
||||
builder: (context, state) {
|
||||
final group = state.hiddenGroups.firstWhereOrNull(
|
||||
(g) => g.groupId == this.group.groupId,
|
||||
);
|
||||
if (group == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return ExpansionTile(
|
||||
tilePadding: EdgeInsets.zero,
|
||||
childrenPadding: EdgeInsets.zero,
|
||||
title: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
group.groupName,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(4),
|
||||
child: FlowySvg(
|
||||
FlowySvgs.hide_m,
|
||||
size: Size.square(20),
|
||||
),
|
||||
),
|
||||
onTap: () => showFlowyMobileConfirmDialog(
|
||||
context,
|
||||
title: LocaleKeys.board_mobile_unhideGroup.tr(),
|
||||
content: LocaleKeys.board_mobile_unhideGroupContent.tr(),
|
||||
actionButtonTitle: LocaleKeys.button_yes.tr(),
|
||||
actionButtonColor: Theme.of(context).colorScheme.primary,
|
||||
onActionButtonPressed: () => context.read<BoardBloc>().add(
|
||||
BoardEvent.toggleGroupVisibility(
|
||||
group,
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
children: [
|
||||
MobileHiddenGroupItemList(
|
||||
bloc: bloc,
|
||||
viewId: databaseController.viewId,
|
||||
groupId: group.groupId,
|
||||
primaryField: primaryField,
|
||||
rowCache: databaseController.rowCache,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MobileHiddenGroupItemList extends StatelessWidget {
|
||||
const MobileHiddenGroupItemList({
|
||||
required this.bloc,
|
||||
required this.groupId,
|
||||
required this.viewId,
|
||||
required this.primaryField,
|
||||
required this.rowCache,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final BoardBloc bloc;
|
||||
final String groupId;
|
||||
final String viewId;
|
||||
final FieldInfo primaryField;
|
||||
final RowCache rowCache;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: bloc,
|
||||
child: BlocBuilder<BoardBloc, BoardState>(
|
||||
builder: (context, state) {
|
||||
final group = state.hiddenGroups.firstWhereOrNull(
|
||||
(g) => g.groupId == groupId,
|
||||
);
|
||||
if (group == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final cells = <Widget>[
|
||||
...group.rows.map(
|
||||
(item) {
|
||||
final cellContext = rowCache.loadCells(item)[primaryField.id]!;
|
||||
final renderHook = RowCardRenderHook<String>();
|
||||
renderHook.addTextCellHook((cellData, _, __) {
|
||||
return BlocBuilder<TextCellBloc, TextCellState>(
|
||||
builder: (context, state) {
|
||||
final text = cellData.isEmpty
|
||||
? LocaleKeys.grid_row_titlePlaceholder.tr()
|
||||
: cellData;
|
||||
|
||||
if (text.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
if (!cellContext.rowMeta.isDocumentEmpty) ...[
|
||||
const FlowySvg(FlowySvgs.notes_s),
|
||||
const HSpace(4),
|
||||
],
|
||||
Expanded(
|
||||
child: Text(
|
||||
text,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
return TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
textStyle: Theme.of(context).textTheme.bodyMedium,
|
||||
foregroundColor: Theme.of(context).colorScheme.onBackground,
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
child: CardCellBuilder<String>(rowCache.cellCache).buildCell(
|
||||
cellContext: cellContext,
|
||||
renderHook: renderHook,
|
||||
hasNotes: !cellContext.rowMeta.isDocumentEmpty,
|
||||
),
|
||||
onPressed: () {
|
||||
context.push(
|
||||
MobileRowDetailPage.routeName,
|
||||
extra: {
|
||||
MobileRowDetailPage.argRowId: item.id,
|
||||
MobileRowDetailPage.argDatabaseController:
|
||||
context.read<BoardBloc>().databaseController,
|
||||
},
|
||||
final cells = group.rows.map(
|
||||
(item) {
|
||||
final cellContext =
|
||||
databaseController.rowCache.loadCells(item).firstWhere(
|
||||
(cellContext) => cellContext.fieldId == primaryField.id,
|
||||
);
|
||||
|
||||
return TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
textStyle: Theme.of(context).textTheme.bodyMedium,
|
||||
foregroundColor: Theme.of(context).colorScheme.onBackground,
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
child: CardCellBuilder(
|
||||
databaseController:
|
||||
context.read<BoardBloc>().databaseController,
|
||||
).build(
|
||||
cellContext: cellContext,
|
||||
styleMap: {FieldType.RichText: _titleCellStyle(context)},
|
||||
hasNotes: !item.isDocumentEmpty,
|
||||
),
|
||||
onPressed: () {
|
||||
context.push(
|
||||
MobileRowDetailPage.routeName,
|
||||
extra: {
|
||||
MobileRowDetailPage.argRowId: item.id,
|
||||
MobileRowDetailPage.argDatabaseController:
|
||||
context.read<BoardBloc>().databaseController,
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
];
|
||||
);
|
||||
},
|
||||
).toList();
|
||||
|
||||
return ListView.builder(
|
||||
itemBuilder: (context, index) => cells[index],
|
||||
itemCount: cells.length,
|
||||
shrinkWrap: true,
|
||||
physics: const ClampingScrollPhysics(),
|
||||
);
|
||||
},
|
||||
),
|
||||
return ExpansionTile(
|
||||
tilePadding: EdgeInsets.zero,
|
||||
childrenPadding: EdgeInsets.zero,
|
||||
title: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
group.groupName,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(4),
|
||||
child: FlowySvg(
|
||||
FlowySvgs.hide_m,
|
||||
size: Size.square(20),
|
||||
),
|
||||
),
|
||||
onTap: () => showFlowyMobileConfirmDialog(
|
||||
context,
|
||||
title: FlowyText(LocaleKeys.board_mobile_unhideGroup.tr()),
|
||||
content: FlowyText(
|
||||
LocaleKeys.board_mobile_unhideGroupContent.tr(),
|
||||
),
|
||||
actionButtonTitle: LocaleKeys.button_yes.tr(),
|
||||
actionButtonColor: Theme.of(context).colorScheme.primary,
|
||||
onActionButtonPressed: () => context.read<BoardBloc>().add(
|
||||
BoardEvent.toggleGroupVisibility(
|
||||
group,
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
children: cells,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
TextCardCellStyle _titleCellStyle(BuildContext context) {
|
||||
return TextCardCellStyle(
|
||||
padding: EdgeInsets.zero,
|
||||
textStyle: Theme.of(context).textTheme.bodyMedium!,
|
||||
maxLines: 2,
|
||||
titleTextStyle: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(fontSize: 11, overflow: TextOverflow.ellipsis),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,2 @@
|
||||
export 'card_detail/mobile_card_detail_screen.dart';
|
||||
export 'card_content/mobile_card_content.dart';
|
||||
export 'mobile_card_content.dart';
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
export 'checkbox.dart';
|
||||
export 'date.dart';
|
||||
export 'style.dart';
|
||||
export 'time_stamp.dart';
|
||||
export 'number.dart';
|
||||
export 'text.dart';
|
||||
export 'select_option.dart';
|
||||
export 'url.dart';
|
||||
export 'checklist.dart';
|
||||
-68
@@ -1,68 +0,0 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/card/cells/card_cell.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/checkbox_cell/checkbox_cell_bloc.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class MobileCheckboxCardCell extends CardCell {
|
||||
const MobileCheckboxCardCell({
|
||||
super.key,
|
||||
required this.cellControllerBuilder,
|
||||
});
|
||||
|
||||
final CellControllerBuilder cellControllerBuilder;
|
||||
|
||||
@override
|
||||
State<MobileCheckboxCardCell> createState() => _CheckboxCellState();
|
||||
}
|
||||
|
||||
class _CheckboxCellState extends State<MobileCheckboxCardCell> {
|
||||
late final CheckboxCellBloc _cellBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as CheckboxCellController;
|
||||
_cellBloc = CheckboxCellBloc(cellController: cellController)
|
||||
..add(const CheckboxCellEvent.initial());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_cellBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<CheckboxCellBloc, CheckboxCellState>(
|
||||
buildWhen: (previous, current) =>
|
||||
previous.isSelected != current.isSelected,
|
||||
builder: (context, state) {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
alignment: Alignment.centerLeft,
|
||||
visualDensity: VisualDensity.compact,
|
||||
icon: FlowySvg(
|
||||
state.isSelected
|
||||
? FlowySvgs.check_filled_s
|
||||
: FlowySvgs.uncheck_s,
|
||||
blendMode: BlendMode.dst,
|
||||
size: const Size.square(24),
|
||||
),
|
||||
onPressed: () => context
|
||||
.read<CheckboxCellBloc>()
|
||||
.add(const CheckboxCellEvent.select()),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
-137
@@ -1,137 +0,0 @@
|
||||
import 'package:appflowy/mobile/presentation/database/card/card_content/card_cells/card_cells.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/card/cells/card_cell.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/checklist_cell/checklist_cell_bloc.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:percent_indicator/linear_percent_indicator.dart';
|
||||
|
||||
class MobileChecklistCardCell extends CardCell {
|
||||
const MobileChecklistCardCell({
|
||||
super.key,
|
||||
required this.cellControllerBuilder,
|
||||
});
|
||||
|
||||
final CellControllerBuilder cellControllerBuilder;
|
||||
|
||||
@override
|
||||
State<MobileChecklistCardCell> createState() => _ChecklistCellState();
|
||||
}
|
||||
|
||||
class _ChecklistCellState extends State<MobileChecklistCardCell> {
|
||||
late final ChecklistCellBloc _cellBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as ChecklistCellController;
|
||||
_cellBloc = ChecklistCellBloc(cellController: cellController)
|
||||
..add(const ChecklistCellEvent.initial());
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_cellBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cellStyle = MobileCardCellStyle(context);
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<ChecklistCellBloc, ChecklistCellState>(
|
||||
builder: (context, state) {
|
||||
if (state.tasks.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Padding(
|
||||
padding: cellStyle.padding,
|
||||
child: MobileChecklistProgressBar(
|
||||
tasks: state.tasks,
|
||||
percent: state.percent,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MobileChecklistProgressBar extends StatefulWidget {
|
||||
const MobileChecklistProgressBar({
|
||||
super.key,
|
||||
required this.tasks,
|
||||
required this.percent,
|
||||
});
|
||||
|
||||
final List<ChecklistSelectOption> tasks;
|
||||
final double percent;
|
||||
final int segmentLimit = 5;
|
||||
|
||||
@override
|
||||
State<MobileChecklistProgressBar> createState() =>
|
||||
_MobileChecklistProgresssBarState();
|
||||
}
|
||||
|
||||
class _MobileChecklistProgresssBarState
|
||||
extends State<MobileChecklistProgressBar> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cellStyle = MobileCardCellStyle(context);
|
||||
final numFinishedTasks = widget.tasks.where((e) => e.isSelected).length;
|
||||
final completedTaskColor = numFinishedTasks == widget.tasks.length
|
||||
? AFThemeExtension.of(context).success
|
||||
: Theme.of(context).colorScheme.primary;
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
if (widget.tasks.isNotEmpty &&
|
||||
widget.tasks.length <= widget.segmentLimit)
|
||||
...List<Widget>.generate(
|
||||
widget.tasks.length,
|
||||
(index) => Flexible(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(2)),
|
||||
color: index < numFinishedTasks
|
||||
? completedTaskColor
|
||||
: AFThemeExtension.of(context).progressBarBGColor,
|
||||
),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 1),
|
||||
height: 6.0,
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
Expanded(
|
||||
child: LinearPercentIndicator(
|
||||
lineHeight: 6.0,
|
||||
percent: widget.percent,
|
||||
padding: EdgeInsets.zero,
|
||||
progressColor: completedTaskColor,
|
||||
backgroundColor:
|
||||
AFThemeExtension.of(context).progressBarBGColor,
|
||||
barRadius: const Radius.circular(2),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 8),
|
||||
child: Text(
|
||||
"${(widget.percent * 100).round()}%",
|
||||
style: cellStyle.secondaryTextStyle(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
-74
@@ -1,74 +0,0 @@
|
||||
import 'package:appflowy/mobile/presentation/database/card/card_content/card_cells/style.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/card/cells/card_cell.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/date_cell/date_cell_bloc.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class MobileDateCardCell<CustomCardData> extends CardCell {
|
||||
const MobileDateCardCell({
|
||||
super.key,
|
||||
required this.cellControllerBuilder,
|
||||
this.renderHook,
|
||||
});
|
||||
|
||||
final CellControllerBuilder cellControllerBuilder;
|
||||
final CellRenderHook<dynamic, CustomCardData>? renderHook;
|
||||
|
||||
@override
|
||||
State<MobileDateCardCell> createState() => _DateCellState();
|
||||
}
|
||||
|
||||
class _DateCellState extends State<MobileDateCardCell> {
|
||||
late final DateCellBloc _cellBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as DateCellController;
|
||||
|
||||
_cellBloc = DateCellBloc(cellController: cellController)
|
||||
..add(const DateCellEvent.initial());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_cellBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cellStyle = MobileCardCellStyle(context);
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<DateCellBloc, DateCellState>(
|
||||
buildWhen: (previous, current) => previous.dateStr != current.dateStr,
|
||||
builder: (context, state) {
|
||||
if (state.dateStr.isEmpty) {
|
||||
return const SizedBox();
|
||||
} else {
|
||||
final Widget? custom = widget.renderHook?.call(
|
||||
state.data,
|
||||
widget.cardData,
|
||||
context,
|
||||
);
|
||||
if (custom != null) {
|
||||
return custom;
|
||||
}
|
||||
|
||||
return Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: cellStyle.padding,
|
||||
child: Text(
|
||||
state.dateStr,
|
||||
style: cellStyle.secondaryTextStyle(),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
-76
@@ -1,76 +0,0 @@
|
||||
import 'package:appflowy/mobile/presentation/database/card/card_content/card_cells/style.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/card/cells/card_cell.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/number_cell/number_cell_bloc.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class MobileNumberCardCell<CustomCardData> extends CardCell {
|
||||
const MobileNumberCardCell({
|
||||
super.key,
|
||||
required this.cellControllerBuilder,
|
||||
CustomCardData? cardData,
|
||||
this.renderHook,
|
||||
});
|
||||
|
||||
final CellRenderHook<String, CustomCardData>? renderHook;
|
||||
final CellControllerBuilder cellControllerBuilder;
|
||||
|
||||
@override
|
||||
State<MobileNumberCardCell> createState() => _NumberCellState();
|
||||
}
|
||||
|
||||
class _NumberCellState extends State<MobileNumberCardCell> {
|
||||
late final NumberCellBloc _cellBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as NumberCellController;
|
||||
|
||||
_cellBloc = NumberCellBloc(cellController: cellController)
|
||||
..add(const NumberCellEvent.initial());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_cellBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cellStyle = MobileCardCellStyle(context);
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<NumberCellBloc, NumberCellState>(
|
||||
buildWhen: (previous, current) =>
|
||||
previous.cellContent != current.cellContent,
|
||||
builder: (context, state) {
|
||||
if (state.cellContent.isEmpty) {
|
||||
return const SizedBox();
|
||||
} else {
|
||||
final Widget? custom = widget.renderHook?.call(
|
||||
state.cellContent,
|
||||
widget.cardData,
|
||||
context,
|
||||
);
|
||||
if (custom != null) {
|
||||
return custom;
|
||||
}
|
||||
|
||||
return Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: cellStyle.padding,
|
||||
child: Text(
|
||||
state.cellContent,
|
||||
style: cellStyle.primaryTextStyle(),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
-147
@@ -1,147 +0,0 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/database/card/card_content/card_cells/style.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/card/cells/card_cell.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/select_option_cell/extension.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/select_option_cell/select_option_cell_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class MobileSelectOptionCardCell<CustomCardData> extends CardCell {
|
||||
const MobileSelectOptionCardCell({
|
||||
super.key,
|
||||
required this.cellControllerBuilder,
|
||||
required CustomCardData? cardData,
|
||||
this.renderHook,
|
||||
});
|
||||
|
||||
final CellControllerBuilder cellControllerBuilder;
|
||||
final CellRenderHook<List<SelectOptionPB>, CustomCardData>? renderHook;
|
||||
|
||||
@override
|
||||
State<MobileSelectOptionCardCell> createState() => _SelectOptionCellState();
|
||||
}
|
||||
|
||||
class _SelectOptionCellState extends State<MobileSelectOptionCardCell> {
|
||||
late final SelectOptionCellBloc _cellBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as SelectOptionCellController;
|
||||
_cellBloc = SelectOptionCellBloc(cellController: cellController)
|
||||
..add(const SelectOptionCellEvent.initial());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_cellBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cellStyle = MobileCardCellStyle(context);
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<SelectOptionCellBloc, SelectOptionCellState>(
|
||||
buildWhen: (previous, current) {
|
||||
return previous.selectedOptions != current.selectedOptions;
|
||||
},
|
||||
builder: (context, state) {
|
||||
final Widget? custom = widget.renderHook?.call(
|
||||
state.selectedOptions,
|
||||
widget.cardData,
|
||||
context,
|
||||
);
|
||||
if (custom != null) {
|
||||
return custom;
|
||||
}
|
||||
|
||||
final children = state.selectedOptions
|
||||
.map(
|
||||
(option) => MobileSelectOptionTag.fromOption(
|
||||
context: context,
|
||||
option: option,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
return IntrinsicHeight(
|
||||
child: Padding(
|
||||
padding: cellStyle.padding,
|
||||
child: SizedBox.expand(
|
||||
child: Wrap(spacing: 4, runSpacing: 2, children: children),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MobileSelectOptionTag extends StatelessWidget {
|
||||
const MobileSelectOptionTag({
|
||||
super.key,
|
||||
required this.name,
|
||||
required this.color,
|
||||
this.onSelected,
|
||||
this.onRemove,
|
||||
});
|
||||
|
||||
factory MobileSelectOptionTag.fromOption({
|
||||
required BuildContext context,
|
||||
required SelectOptionPB option,
|
||||
VoidCallback? onSelected,
|
||||
Function(String)? onRemove,
|
||||
}) {
|
||||
return MobileSelectOptionTag(
|
||||
name: option.name,
|
||||
color: option.color.toColor(context),
|
||||
onSelected: onSelected,
|
||||
onRemove: onRemove,
|
||||
);
|
||||
}
|
||||
|
||||
final String name;
|
||||
final Color color;
|
||||
final VoidCallback? onSelected;
|
||||
final void Function(String)? onRemove;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cellStyle = MobileCardCellStyle(context);
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
name,
|
||||
style: cellStyle.tagTextStyle(),
|
||||
),
|
||||
),
|
||||
if (onRemove != null) ...[
|
||||
const HSpace(2),
|
||||
IconButton(
|
||||
onPressed: () => onRemove?.call(name),
|
||||
icon: const FlowySvg(
|
||||
FlowySvgs.close_s,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MobileCardCellStyle {
|
||||
MobileCardCellStyle(this.context);
|
||||
|
||||
BuildContext context;
|
||||
|
||||
EdgeInsets get padding => const EdgeInsets.symmetric(
|
||||
vertical: 4,
|
||||
);
|
||||
|
||||
TextStyle? primaryTextStyle() {
|
||||
final theme = Theme.of(context);
|
||||
return theme.textTheme.bodyMedium?.copyWith(
|
||||
fontSize: 16,
|
||||
color: theme.colorScheme.onBackground,
|
||||
);
|
||||
}
|
||||
|
||||
TextStyle? secondaryTextStyle() {
|
||||
final theme = Theme.of(context);
|
||||
return theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.colorScheme.tertiary,
|
||||
fontSize: 14,
|
||||
);
|
||||
}
|
||||
|
||||
TextStyle? tagTextStyle() {
|
||||
final theme = Theme.of(context);
|
||||
return theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.colorScheme.onBackground,
|
||||
fontSize: 12,
|
||||
);
|
||||
}
|
||||
|
||||
TextStyle? urlTextStyle() {
|
||||
final theme = Theme.of(context);
|
||||
return theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.colorScheme.primary,
|
||||
fontSize: 16,
|
||||
decoration: TextDecoration.underline,
|
||||
);
|
||||
}
|
||||
}
|
||||
-79
@@ -1,79 +0,0 @@
|
||||
import 'package:appflowy/mobile/presentation/database/card/card_content/card_cells/style.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/card/cells/card_cell.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/text_cell/text_cell_bloc.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class MobileTextCardCell<CustomCardData> extends CardCell {
|
||||
const MobileTextCardCell({
|
||||
super.key,
|
||||
required this.cellControllerBuilder,
|
||||
CustomCardData? cardData,
|
||||
this.renderHook,
|
||||
});
|
||||
|
||||
final CellRenderHook<String, CustomCardData>? renderHook;
|
||||
final CellControllerBuilder cellControllerBuilder;
|
||||
|
||||
@override
|
||||
State<MobileTextCardCell> createState() => _MobileTextCardCellState();
|
||||
}
|
||||
|
||||
class _MobileTextCardCellState extends State<MobileTextCardCell> {
|
||||
late final TextCellBloc _cellBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as TextCellController;
|
||||
|
||||
_cellBloc = TextCellBloc(cellController: cellController)
|
||||
..add(const TextCellEvent.initial());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_cellBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cellStyle = MobileCardCellStyle(context);
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<TextCellBloc, TextCellState>(
|
||||
buildWhen: (previous, current) => previous.content != current.content,
|
||||
builder: (context, state) {
|
||||
// return custom widget if render hook is provided(for example, the title of the card on board view)
|
||||
// if widget.cardData.isEmpty means there is no data for this cell
|
||||
final Widget? custom = widget.renderHook?.call(
|
||||
state.content,
|
||||
widget.cardData,
|
||||
context,
|
||||
);
|
||||
if (custom != null) {
|
||||
return custom;
|
||||
}
|
||||
|
||||
// if there is no render hook
|
||||
// the empty text cell will be hidden
|
||||
if (state.content.isEmpty) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
return Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: cellStyle.padding,
|
||||
child: Text(
|
||||
state.content,
|
||||
style: cellStyle.primaryTextStyle(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
-73
@@ -1,73 +0,0 @@
|
||||
import 'package:appflowy/mobile/presentation/database/card/card_content/card_cells/style.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/card/cells/card_cell.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/timestamp_cell/timestamp_cell_bloc.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class MobileTimestampCardCell<CustomCardData> extends CardCell {
|
||||
const MobileTimestampCardCell({
|
||||
super.key,
|
||||
required this.cellControllerBuilder,
|
||||
this.renderHook,
|
||||
});
|
||||
|
||||
final CellControllerBuilder cellControllerBuilder;
|
||||
final CellRenderHook<dynamic, CustomCardData>? renderHook;
|
||||
|
||||
@override
|
||||
State<MobileTimestampCardCell> createState() => _TimestampCellState();
|
||||
}
|
||||
|
||||
class _TimestampCellState extends State<MobileTimestampCardCell> {
|
||||
late final TimestampCellBloc _cellBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as TimestampCellController;
|
||||
|
||||
_cellBloc = TimestampCellBloc(cellController: cellController)
|
||||
..add(const TimestampCellEvent.initial());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_cellBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cellStyle = MobileCardCellStyle(context);
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<TimestampCellBloc, TimestampCellState>(
|
||||
buildWhen: (previous, current) => previous.dateStr != current.dateStr,
|
||||
builder: (context, state) {
|
||||
if (state.dateStr.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
final Widget? custom = widget.renderHook?.call(
|
||||
state.data,
|
||||
widget.cardData,
|
||||
context,
|
||||
);
|
||||
if (custom != null) {
|
||||
return custom;
|
||||
}
|
||||
|
||||
return Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: cellStyle.padding,
|
||||
child: Text(
|
||||
state.dateStr,
|
||||
style: cellStyle.secondaryTextStyle(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
-63
@@ -1,63 +0,0 @@
|
||||
import 'package:appflowy/mobile/presentation/database/card/card_content/card_cells/style.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/card/cells/card_cell.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/url_cell/url_cell_bloc.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class MobileURLCardCell<CustomCardData> extends CardCell {
|
||||
const MobileURLCardCell({
|
||||
super.key,
|
||||
required this.cellControllerBuilder,
|
||||
});
|
||||
|
||||
final CellControllerBuilder cellControllerBuilder;
|
||||
|
||||
@override
|
||||
State<MobileURLCardCell> createState() => _URLCellState();
|
||||
}
|
||||
|
||||
class _URLCellState extends State<MobileURLCardCell> {
|
||||
late final URLCellBloc _cellBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as URLCellController;
|
||||
|
||||
_cellBloc = URLCellBloc(cellController: cellController)
|
||||
..add(const URLCellEvent.initial());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_cellBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cellStyle = MobileCardCellStyle(context);
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<URLCellBloc, URLCellState>(
|
||||
buildWhen: (previous, current) => previous.content != current.content,
|
||||
builder: (context, state) {
|
||||
if (state.content.isEmpty) {
|
||||
return const SizedBox();
|
||||
} else {
|
||||
return Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: cellStyle.padding,
|
||||
child: Text(
|
||||
state.content,
|
||||
style: cellStyle.urlTextStyle(),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
-108
@@ -1,108 +0,0 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_service.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/card/card.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/card/card_cell_builder.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/card/cells/card_cell.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MobileCardContent<CustomCardData> extends StatelessWidget {
|
||||
const MobileCardContent({
|
||||
super.key,
|
||||
required this.cellBuilder,
|
||||
required this.cells,
|
||||
required this.cardData,
|
||||
required this.styleConfiguration,
|
||||
this.renderHook,
|
||||
});
|
||||
|
||||
final CardCellBuilder<CustomCardData> cellBuilder;
|
||||
|
||||
final List<DatabaseCellContext> cells;
|
||||
final RowCardRenderHook<CustomCardData>? renderHook;
|
||||
final CustomCardData? cardData;
|
||||
final RowCardStyleConfiguration styleConfiguration;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: styleConfiguration.cardPadding,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: _makeCells(context, cells),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _makeCells(
|
||||
BuildContext context,
|
||||
List<DatabaseCellContext> cells,
|
||||
) {
|
||||
final List<Widget> children = [];
|
||||
|
||||
cells.asMap().forEach((int index, DatabaseCellContext cellContext) {
|
||||
Widget child;
|
||||
if (index == 0) {
|
||||
// The title cell UI is different with a normal text cell.
|
||||
// Use render hook to customize its UI
|
||||
child = _buildTitleCell(cellContext);
|
||||
} else {
|
||||
child = Padding(
|
||||
key: cellContext.key(),
|
||||
padding: styleConfiguration.cellPadding,
|
||||
child: cellBuilder.buildCell(
|
||||
cellContext: cellContext,
|
||||
cardData: cardData,
|
||||
renderHook: renderHook,
|
||||
hasNotes: !cellContext.rowMeta.isDocumentEmpty,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
children.add(child);
|
||||
});
|
||||
return children;
|
||||
}
|
||||
|
||||
Widget _buildTitleCell(
|
||||
DatabaseCellContext cellContext,
|
||||
) {
|
||||
final renderHook = RowCardRenderHook<String>();
|
||||
renderHook.addTextCellHook((cellData, cardData, context) {
|
||||
final text = cellData.isEmpty
|
||||
? LocaleKeys.grid_row_titlePlaceholder.tr()
|
||||
: cellData;
|
||||
final color = cellData.isEmpty
|
||||
? Theme.of(context).hintColor
|
||||
: Theme.of(context).colorScheme.onBackground;
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
if (!cellContext.rowMeta.isDocumentEmpty) ...[
|
||||
const FlowySvg(FlowySvgs.notes_s),
|
||||
const HSpace(4),
|
||||
],
|
||||
Expanded(
|
||||
child: FlowyText.medium(
|
||||
text,
|
||||
color: color,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
return Padding(
|
||||
key: cellContext.key(),
|
||||
padding: styleConfiguration.cellPadding,
|
||||
child: CardCellBuilder<String>(cellBuilder.cellCache).buildCell(
|
||||
cellContext: cellContext,
|
||||
renderHook: renderHook,
|
||||
hasNotes: !cellContext.rowMeta.isDocumentEmpty,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loaded 100 of 1321 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user