Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
055af03320 | ||
|
|
879d73a27a | ||
|
|
6721caf2fa | ||
|
|
7bd876bf85 | ||
|
|
2f3c9c11b1 | ||
|
|
5d42d03e15 | ||
|
|
38d69c0b58 | ||
|
|
7a0c6a829b | ||
|
|
d1d485ee94 | ||
|
|
1b15b8fa64 | ||
|
|
614f3ce81b | ||
|
|
006ea02bfe | ||
|
|
3caf84b8a0 | ||
|
|
a002cb7428 | ||
|
|
96e39d0257 | ||
|
|
944da858a8 | ||
|
|
53a2b7cb6e | ||
|
|
ec799afb7d | ||
|
|
06c356c7a1 | ||
|
|
dbe17aa4e8 | ||
|
|
187c5892ea | ||
|
|
c8be43134c | ||
|
|
e615e7a304 | ||
|
|
e3cd9a09f3 | ||
|
|
decc438b8a | ||
|
|
a1793b53dd | ||
|
|
2b9b8c19a9 | ||
|
|
91173f707e | ||
|
|
a1bc9246b1 | ||
|
|
3e91be4960 | ||
|
|
9930efdc84 | ||
|
|
93ca6ac906 | ||
|
|
c400fdc01d | ||
|
|
4f592e80b6 | ||
|
|
7ea71fc6ae | ||
|
|
8688183dae | ||
|
|
b5a17ee0bd | ||
|
|
1ecb864db0 | ||
|
|
7b8af686e0 | ||
|
|
1c6e709c82 | ||
|
|
0fd0483302 | ||
|
|
aa621289e9 | ||
|
|
009c2ed402 | ||
|
|
08e2be367c | ||
|
|
31567d451f | ||
|
|
8e34951477 | ||
|
|
589f5c1298 | ||
|
|
d1127a9bfa | ||
|
|
71bb182803 | ||
|
|
3161dddb25 | ||
|
|
d9dc132e5b | ||
|
|
49ba1fd246 | ||
|
|
e9b2cbb317 | ||
|
|
2bb33e1d2a | ||
|
|
b7db1e7dda | ||
|
|
9da71c3186 | ||
|
|
a6cce62b06 | ||
|
|
f7c6b8792f | ||
|
|
735cddd64b | ||
|
|
94c033bba8 | ||
|
|
4b0368d552 | ||
|
|
dda798752f | ||
|
|
e561778526 | ||
|
|
ed2f2360fe | ||
|
|
08bf5db2de | ||
|
|
080b7db605 | ||
|
|
1cc41c10c4 | ||
|
|
6ce77aea3e | ||
|
|
01d7d6e900 | ||
|
|
d264f3dbde | ||
|
|
8139065113 | ||
|
|
3324e7837b | ||
|
|
f20f8bcfbf | ||
|
|
c20ed8c019 | ||
|
|
0b658bff0b | ||
|
|
8319606cc0 | ||
|
|
eefdf96b00 | ||
|
|
e85e092db8 | ||
|
|
928da2a223 | ||
|
|
78c2e756d6 | ||
|
|
47c2ae23ed | ||
|
|
0fd0900b41 | ||
|
|
61ad75502f | ||
|
|
29858dda7a | ||
|
|
34c441f3ad | ||
|
|
c3114e5a39 | ||
|
|
d89804f3e4 | ||
|
|
9209562648 | ||
|
|
9ee8cc6a7b | ||
|
|
9a295daf99 | ||
|
|
956d62fe82 | ||
|
|
7541dff00e | ||
|
|
c4cdcbff73 | ||
|
|
b77fdb8424 | ||
|
|
083c0d0f0b | ||
|
|
d25efba292 | ||
|
|
40e627c303 | ||
|
|
e3a68d3ecb | ||
|
|
1b185ba3cd | ||
|
|
e5ad0f6d1d | ||
|
|
93bf1f79f6 | ||
|
|
f342f5ec7e | ||
|
|
12cb9bde39 | ||
|
|
b649950d62 | ||
|
|
2ef74c229c | ||
|
|
62f0307289 | ||
|
|
242faee2f5 | ||
|
|
7626cfd546 | ||
|
|
5b2df9e482 | ||
|
|
2a2dc903c1 | ||
|
|
d1ed45c312 | ||
|
|
a487aa74fd | ||
|
|
d3b7c5fea5 | ||
|
|
3fa72106e9 | ||
|
|
8ae67c5098 | ||
|
|
a206d9aa8c | ||
|
|
9e93483113 | ||
|
|
00690a1bb8 | ||
|
|
86be92ba1b | ||
|
|
6305ab8c5d | ||
|
|
c371c6cd63 | ||
|
|
190e3bedda | ||
|
|
a78752d427 | ||
|
|
b2b72d2130 | ||
|
|
104bf12ac7 | ||
|
|
0e844678fc | ||
|
|
23968d89fc | ||
|
|
0ce43ca5fa | ||
|
|
b9a34f6fc2 | ||
|
|
93a110d37d | ||
|
|
70d6351a6c | ||
|
|
70e96c01b3 | ||
|
|
6a0650e6d5 | ||
|
|
6d09c33782 | ||
|
|
faf1e98d15 | ||
|
|
58b17a939c | ||
|
|
7113269802 | ||
|
|
e460120a1c | ||
|
|
d0ce65f711 | ||
|
|
5878379b2e | ||
|
|
fd5299a13d | ||
|
|
c2d7c5360d | ||
|
|
9853fbfc10 | ||
|
|
44fb610269 | ||
|
|
e6bf6a5c7d | ||
|
|
d3d929b68e | ||
|
|
f7a2d9e581 | ||
|
|
6283649a6b | ||
|
|
88cc0caab7 | ||
|
|
7eb8ea347d | ||
|
|
8935b7158c | ||
|
|
fa230907ca | ||
|
|
6d496b2088 | ||
|
|
4b24b41dd4 | ||
|
|
b3a0119c18 | ||
|
|
463c8c7ee4 | ||
|
|
e2359cf047 | ||
|
|
d23977ebb0 | ||
|
|
f1ad03eaa9 | ||
|
|
17c9c9b556 | ||
|
|
93f9a2cab1 | ||
|
|
4b710527c9 | ||
|
|
6e26dc128c | ||
|
|
d3d9ab2fb0 | ||
|
|
1db8480b75 | ||
|
|
7e53b34484 | ||
|
|
34465efc24 | ||
|
|
a29b170b13 | ||
|
|
14b60fb9b0 | ||
|
|
1c638dd930 | ||
|
|
55a4810d60 | ||
|
|
2bc7875bdd | ||
|
|
f7adcae8ff | ||
|
|
23997e977c | ||
|
|
3ff47b7e1e | ||
|
|
d33af70a5c | ||
|
|
510752868d | ||
|
|
42d1bb84c5 | ||
|
|
2debd0283c | ||
|
|
e87ade6b3f | ||
|
|
e2a923a796 | ||
|
|
cd0f8d80e9 | ||
|
|
7abe9f4661 | ||
|
|
758c304a74 | ||
|
|
f84473c857 | ||
|
|
a26b2a356c | ||
|
|
57bbb6cc41 | ||
|
|
e82edc0419 | ||
|
|
f57297e76d | ||
|
|
7769034467 | ||
|
|
a523b8ff90 | ||
|
|
7b7b907017 | ||
|
|
29b262a1c6 | ||
|
|
e28a251e72 | ||
|
|
98b7882d43 | ||
|
|
e279ad1cc7 | ||
|
|
a798b037db | ||
|
|
5fbaf664ba | ||
|
|
41a346c7ed | ||
|
|
aab942d163 | ||
|
|
5757cc9a1d | ||
|
|
453e6309d5 | ||
|
|
4041724980 | ||
|
|
d378c456d4 | ||
|
|
0abf916796 | ||
|
|
393850ae4b | ||
|
|
46bad4e7e8 | ||
|
|
e9fc003e10 | ||
|
|
cb60488bbe | ||
|
|
04556252e1 | ||
|
|
c2e8a12427 | ||
|
|
73421e0d58 | ||
|
|
b9fd3701cd | ||
|
|
9fbba5fb60 | ||
|
|
7261d1e8da | ||
|
|
27aac2b911 | ||
|
|
0cd5af5ffa | ||
|
|
87e950733f | ||
|
|
2402b4c6f1 | ||
|
|
ed81a0aff2 | ||
|
|
dce9231118 | ||
|
|
d1c1449cf6 | ||
|
|
7c3dd5375d | ||
|
|
81532d014e | ||
|
|
735a09b333 | ||
|
|
d52a04a9e3 | ||
|
|
a14ced458c | ||
|
|
5250a151c8 | ||
|
|
d5a5a64fcf | ||
|
|
175c90e379 | ||
|
|
115ae27ab1 | ||
|
|
e908892b4a | ||
|
|
948d61a9e0 | ||
|
|
b5c4786896 | ||
|
|
d24f1c566a | ||
|
|
ddf68b010d | ||
|
|
22b108df70 | ||
|
|
aff9a61919 | ||
|
|
043cd3f3bb | ||
|
|
29fb4af40a | ||
|
|
a2e211555e | ||
|
|
d1af172fb7 | ||
|
|
23b6f94e82 | ||
|
|
82fffba45a | ||
|
|
0373088fb8 | ||
|
|
0da0f320b7 | ||
|
|
4abdcd9478 | ||
|
|
1e54c2dc7c | ||
|
|
01747f13e8 | ||
|
|
4a5eda6eeb | ||
|
|
b5d799655a | ||
|
|
43b250ec28 | ||
|
|
20e82880ff | ||
|
|
661a0879c6 | ||
|
|
2fb18dd051 | ||
|
|
6334255e15 | ||
|
|
f66821715f | ||
|
|
620e027c3e | ||
|
|
864768b3ba | ||
|
|
a8b4f22703 | ||
|
|
432db0f6d5 | ||
|
|
e8e4162a5c | ||
|
|
241234726f | ||
|
|
e426970eed | ||
|
|
d5544af6c5 | ||
|
|
9361afd573 | ||
|
|
dc93a336d6 | ||
|
|
4e5482488f | ||
|
|
5bbf174ffd | ||
|
|
f36e3ae378 | ||
|
|
dc6349b4b1 | ||
|
|
44d8def3ce | ||
|
|
c6ad57f11d | ||
|
|
ff23165d3e | ||
|
|
253e7597c4 | ||
|
|
d2e3fdfefd | ||
|
|
a2336f5bf7 | ||
|
|
8833df1740 | ||
|
|
1524b10a8a | ||
|
|
fe0fa9b530 | ||
|
|
80afcf44c0 | ||
|
|
e500c89978 | ||
|
|
079b9888a8 | ||
|
|
0fe383e538 | ||
|
|
c006e29afc | ||
|
|
655a2b7093 | ||
|
|
23c67bcdba | ||
|
|
521fffd97c | ||
|
|
2ecc2a67a9 | ||
|
|
2d11215bb2 | ||
|
|
6d0c9f766b | ||
|
|
7a3f013a87 | ||
|
|
8c1520b273 | ||
|
|
a7b850e752 | ||
|
|
42dc6017c6 | ||
|
|
f9fba6b561 | ||
|
|
46896b5a2b | ||
|
|
2b8dca209e | ||
|
|
c78f23e1c0 | ||
|
|
50f5be3e75 | ||
|
|
e1c68c1b72 | ||
|
|
3bcadff152 | ||
|
|
b09ff040f1 | ||
|
|
bf087bba5d | ||
|
|
605a53f2ae | ||
|
|
ecc5555a4e | ||
|
|
d2a5bab6d9 | ||
|
|
a1dec0f269 | ||
|
|
b24fbc6b60 | ||
|
|
bbb3f95a13 | ||
|
|
7859fc7922 | ||
|
|
f812040f04 | ||
|
|
7d96c2dfd4 | ||
|
|
e4132ed217 | ||
|
|
a7f40b2adc | ||
|
|
c996c9c28e | ||
|
|
f59b8a2e5c | ||
|
|
f7480cba16 | ||
|
|
271b250eac | ||
|
|
6b1e7b6ac8 | ||
|
|
bf2a00b133 | ||
|
|
a8ed93054c | ||
|
|
b9ad2768cf | ||
|
|
54c9d12171 | ||
|
|
40312f4260 | ||
|
|
d2ca41c8f5 |
No files matched your search
@@ -1,72 +0,0 @@
|
||||
name: Deploy Web (Test)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- build/test
|
||||
env:
|
||||
NODE_VERSION: "18.16.0"
|
||||
PNPM_VERSION: "8.5.0"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.WEB_TEST_SSH_PRIVATE_KEY }}
|
||||
REMOTE_HOST: ${{ secrets.WEB_TEST_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.WEB_TEST_REMOTE_USER }}
|
||||
SSL_CERTIFICATE: ${{ secrets.WEB_TEST_SSL_CERTIFICATE }}
|
||||
SSL_CERTIFICATE_KEY: ${{ secrets.WEB_TEST_SSL_CERTIFICATE_KEY }}
|
||||
ENV_FILE: test.env
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
- name: setup pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
- name: Node_modules cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: frontend/appflowy_web_app/node_modules
|
||||
key: node-modules-${{ runner.os }}
|
||||
- name: install frontend dependencies
|
||||
working-directory: frontend/appflowy_web_app
|
||||
run: |
|
||||
pnpm install
|
||||
- name: copy env file
|
||||
working-directory: frontend/appflowy_web_app
|
||||
run: |
|
||||
cp ${{ env.ENV_FILE }} .env
|
||||
- name: test and lint
|
||||
working-directory: frontend/appflowy_web_app
|
||||
run: |
|
||||
pnpm run lint
|
||||
- name: build
|
||||
working-directory: frontend/appflowy_web_app
|
||||
run: |
|
||||
pnpm run build
|
||||
- name: generate SSL certificate
|
||||
run: |
|
||||
echo "${{ env.SSL_CERTIFICATE }}" > nginx-signed.crt
|
||||
echo "${{ env.SSL_CERTIFICATE_KEY }}" > nginx-signed.key
|
||||
- name: Deploy to EC2
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
with:
|
||||
SSH_PRIVATE_KEY: ${{ env.SSH_PRIVATE_KEY }}
|
||||
ARGS: "-rlgoDzvc -i"
|
||||
SOURCE: "frontend/appflowy_web_app/dist frontend/appflowy_web_app/server.cjs frontend/appflowy_web_app/start.sh frontend/appflowy_web_app/Dockerfile frontend/appflowy_web_app/nginx.conf frontend/appflowy_web_app/.env nginx-signed.crt nginx-signed.key"
|
||||
REMOTE_HOST: ${{ env.REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ env.REMOTE_USER }}
|
||||
EXCLUDE: "frontend/appflowy_web_app/dist/, frontend/appflowy_web_app/node_modules/"
|
||||
SCRIPT_AFTER: |
|
||||
docker build -t appflowy-web-app .
|
||||
docker rm -f appflowy-web-app || true
|
||||
docker run -d -p 80:80 -p 443:443 --name appflowy-web-app appflowy-web-app
|
||||
@@ -13,7 +13,7 @@ on:
|
||||
- release/*
|
||||
paths:
|
||||
- frontend/**
|
||||
types: [opened, synchronize, reopened, unlocked, ready_for_review]
|
||||
types: [ opened, synchronize, reopened, unlocked, ready_for_review ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
|
||||
@@ -246,12 +246,27 @@ jobs:
|
||||
- name: Run Docker-Compose
|
||||
working-directory: AppFlowy-Cloud
|
||||
env:
|
||||
BACKEND_VERSION: 0.3.24-amd64
|
||||
APPFLOWY_CLOUD_VERSION: 0.6.4-amd64
|
||||
run: |
|
||||
docker compose down -v --remove-orphans
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
sleep 10
|
||||
container_id=$(docker ps --filter name=appflowy-cloud-appflowy_cloud-1 -q)
|
||||
if [ -z "$container_id" ]; then
|
||||
echo "AppFlowy-Cloud container is not running. Pulling and starting the container..."
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
echo "Waiting for the container to be ready..."
|
||||
sleep 10
|
||||
else
|
||||
running_image=$(docker inspect --format='{{index .Config.Image}}' "$container_id")
|
||||
if [ "$running_image" != "appflowy-cloud:$APPFLOWY_CLOUD_VERSION" ]; then
|
||||
echo "AppFlowy-Cloud is running with an incorrect version. Pulling the correct version..."
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
echo "Waiting for the container to be ready..."
|
||||
sleep 10
|
||||
else
|
||||
echo "AppFlowy-Cloud is running with the correct version."
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -21,27 +21,42 @@ on:
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.22.0"
|
||||
RUST_TOOLCHAIN: "1.77.2"
|
||||
RUST_TOOLCHAIN: "1.80.1"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.event.pull_request.draft != true
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os: [ macos-14 ]
|
||||
runs-on: ${{ matrix.os }}
|
||||
build-self-hosted:
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
runs-on: self-hosted
|
||||
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Build AppFlowy
|
||||
working-directory: frontend
|
||||
run: |
|
||||
cargo make --profile development-ios-arm64-sim appflowy-core-dev-ios
|
||||
cargo make --profile development-ios-arm64-sim code_generation
|
||||
|
||||
- uses: futureware-tech/simulator-action@v3
|
||||
id: simulator-action
|
||||
with:
|
||||
model: "iPhone 15"
|
||||
shutdown_after_job: false
|
||||
|
||||
build-macos:
|
||||
if: github.event.pull_request.head.repo.full_name != github.repository
|
||||
runs-on: macos-13
|
||||
|
||||
steps:
|
||||
- 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 }}
|
||||
@@ -49,8 +64,7 @@ jobs:
|
||||
override: true
|
||||
profile: minimal
|
||||
|
||||
- name: Install flutter
|
||||
id: flutter
|
||||
- name: Install Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
@@ -59,13 +73,13 @@ jobs:
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
prefix-key: ${{ matrix.os }}
|
||||
prefix-key: macos-latest
|
||||
workspaces: |
|
||||
frontend/rust-lib
|
||||
|
||||
- uses: davidB/rust-cargo-make@v1
|
||||
with:
|
||||
version: "0.36.6"
|
||||
version: "0.37.15"
|
||||
|
||||
- name: Install prerequisites
|
||||
working-directory: frontend
|
||||
@@ -85,7 +99,7 @@ jobs:
|
||||
- uses: futureware-tech/simulator-action@v3
|
||||
id: simulator-action
|
||||
with:
|
||||
model: 'iPhone 15'
|
||||
model: "iPhone 15"
|
||||
shutdown_after_job: false
|
||||
|
||||
# - name: Run AppFlowy on simulator
|
||||
|
||||
@@ -135,7 +135,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
job:
|
||||
- { target: x86_64-apple-darwin, os: macos-11, extra-build-args: "" }
|
||||
- { target: x86_64-apple-darwin, os: macos-12, extra-build-args: "" }
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v4
|
||||
@@ -233,7 +233,7 @@ jobs:
|
||||
job:
|
||||
- {
|
||||
targets: "aarch64-apple-darwin,x86_64-apple-darwin",
|
||||
os: macos-11,
|
||||
os: macos-latest,
|
||||
extra-build-args: "",
|
||||
}
|
||||
steps:
|
||||
@@ -479,6 +479,24 @@ jobs:
|
||||
cache-from: type=registry,ref=${{ secrets.DOCKER_HUB_USERNAME }}/af_build_cache:buildcache
|
||||
cache-to: type=registry,ref=${{ secrets.DOCKER_HUB_USERNAME }}/af_build_cache:buildcache,mode=max
|
||||
|
||||
notify-failure:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build-for-macOS-x86_64
|
||||
- build-for-windows
|
||||
- build-for-linux
|
||||
if: failure()
|
||||
steps:
|
||||
- uses: 8398a7/action-slack@v3
|
||||
with:
|
||||
status: ${{ job.status }}
|
||||
text: |
|
||||
🔴🔴🔴Workflow ${{ github.workflow }} in repository ${{ github.repository }} was failed 🔴🔴🔴.
|
||||
fields: repo,message,author,eventName,ref,workflow
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.RELEASE_SLACK_WEBHOOK }}
|
||||
if: always()
|
||||
|
||||
notify-discord:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
|
||||
@@ -8,6 +8,7 @@ on:
|
||||
- "release/*"
|
||||
paths:
|
||||
- "frontend/rust-lib/**"
|
||||
- ".github/workflows/rust_ci.yaml"
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
@@ -22,70 +23,50 @@ env:
|
||||
RUST_TOOLCHAIN: "1.77.2"
|
||||
|
||||
jobs:
|
||||
test-on-ubuntu:
|
||||
runs-on: ubuntu-latest
|
||||
self-hosted-job:
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
# - name: Maximize build space
|
||||
# uses: easimon/maximize-build-space@master
|
||||
# with:
|
||||
# root-reserve-mb: 2048
|
||||
# swap-size-mb: 1024
|
||||
# remove-dotnet: 'true'
|
||||
|
||||
# the following step is required to avoid running out of space
|
||||
- name: Maximize build space
|
||||
run: |
|
||||
sudo rm -rf /usr/share/dotnet
|
||||
sudo rm -rf /opt/ghc
|
||||
sudo rm -rf "/usr/local/share/boost"
|
||||
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
||||
sudo docker image prune --all --force
|
||||
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust toolchain
|
||||
id: rust_toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
profile: minimal
|
||||
|
||||
- name: Install prerequisites
|
||||
working-directory: frontend
|
||||
run: |
|
||||
cargo install --force cargo-make
|
||||
cargo install --force duckscript_cli
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
prefix-key: "ubuntu-latest"
|
||||
workspaces: |
|
||||
frontend/rust-lib
|
||||
|
||||
- name: Checkout appflowy cloud code
|
||||
- name: Checkout Appflowy Cloud
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: AppFlowy-IO/AppFlowy-Cloud
|
||||
path: AppFlowy-Cloud
|
||||
|
||||
- name: Prepare appflowy cloud env
|
||||
- name: Prepare Appflowy Cloud env
|
||||
working-directory: AppFlowy-Cloud
|
||||
run: |
|
||||
# log level
|
||||
cp deploy.env .env
|
||||
sed -i 's|RUST_LOG=.*|RUST_LOG=trace|' .env
|
||||
sed -i 's|API_EXTERNAL_URL=.*|API_EXTERNAL_URL=http://localhost|' .env
|
||||
sed -i '' 's|RUST_LOG=.*|RUST_LOG=trace|' .env
|
||||
sed -i '' 's|API_EXTERNAL_URL=.*|API_EXTERNAL_URL=http://localhost|' .env
|
||||
|
||||
- name: Run Docker-Compose
|
||||
- name: Ensure AppFlowy-Cloud is Running with Correct Version
|
||||
working-directory: AppFlowy-Cloud
|
||||
env:
|
||||
BACKEND_VERSION: 0.3.24-amd64
|
||||
APPFLOWY_CLOUD_VERSION: 0.6.4-amd64
|
||||
run: |
|
||||
docker pull appflowyinc/appflowy_cloud:latest
|
||||
docker compose up -d
|
||||
container_id=$(docker ps --filter name=appflowy-cloud-appflowy_cloud-1 -q)
|
||||
if [ -z "$container_id" ]; then
|
||||
echo "AppFlowy-Cloud container is not running. Pulling and starting the container..."
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
echo "Waiting for the container to be ready..."
|
||||
sleep 10
|
||||
else
|
||||
running_image=$(docker inspect --format='{{index .Config.Image}}' "$container_id")
|
||||
if [ "$running_image" != "appflowy-cloud:$APPFLOWY_CLOUD_VERSION" ]; then
|
||||
echo "AppFlowy-Cloud is running with an incorrect version. Pulling the correct version..."
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
echo "Waiting for the container to be ready..."
|
||||
sleep 10
|
||||
else
|
||||
echo "AppFlowy-Cloud is running with the correct version."
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Run rust-lib tests
|
||||
working-directory: frontend/rust-lib
|
||||
@@ -106,6 +87,98 @@ jobs:
|
||||
run: cargo clippy --all-targets -- -D warnings
|
||||
working-directory: frontend/rust-lib
|
||||
|
||||
ubuntu-job:
|
||||
if: github.event.pull_request.head.repo.full_name != github.repository
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Maximize build space
|
||||
run: |
|
||||
sudo rm -rf /usr/share/dotnet
|
||||
sudo rm -rf /opt/ghc
|
||||
sudo rm -rf "/usr/local/share/boost"
|
||||
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
||||
sudo docker image prune --all --force
|
||||
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
profile: minimal
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
prefix-key: ${{ runner.os }}
|
||||
cache-on-failure: true
|
||||
workspaces: |
|
||||
frontend/rust-lib
|
||||
|
||||
- name: Checkout appflowy cloud code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: AppFlowy-IO/AppFlowy-Cloud
|
||||
path: AppFlowy-Cloud
|
||||
|
||||
- name: Prepare appflowy cloud env
|
||||
working-directory: AppFlowy-Cloud
|
||||
run: |
|
||||
cp deploy.env .env
|
||||
sed -i 's|RUST_LOG=.*|RUST_LOG=trace|' .env
|
||||
sed -i 's|API_EXTERNAL_URL=.*|API_EXTERNAL_URL=http://localhost|' .env
|
||||
|
||||
- name: Ensure AppFlowy-Cloud is Running with Correct Version
|
||||
working-directory: AppFlowy-Cloud
|
||||
env:
|
||||
APPFLOWY_CLOUD_VERSION: 0.6.4-amd64
|
||||
run: |
|
||||
container_id=$(docker ps --filter name=appflowy-cloud-appflowy_cloud-1 -q)
|
||||
if [ -z "$container_id" ]; then
|
||||
echo "AppFlowy-Cloud container is not running. Pulling and starting the container..."
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
echo "Waiting for the container to be ready..."
|
||||
sleep 10
|
||||
else
|
||||
running_image=$(docker inspect --format='{{index .Config.Image}}' "$container_id")
|
||||
if [ "$running_image" != "appflowy-cloud:$APPFLOWY_CLOUD_VERSION" ]; then
|
||||
echo "AppFlowy-Cloud is running with an incorrect version. Pulling the correct version..."
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
echo "Waiting for the container to be ready..."
|
||||
sleep 10
|
||||
else
|
||||
echo "AppFlowy-Cloud is running with the correct version."
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Run rust-lib tests
|
||||
working-directory: frontend/rust-lib
|
||||
env:
|
||||
RUST_LOG: info
|
||||
RUST_BACKTRACE: 1
|
||||
af_cloud_test_base_url: http://localhost
|
||||
af_cloud_test_ws_url: ws://localhost/ws/v1
|
||||
af_cloud_test_gotrue_url: http://localhost/gotrue
|
||||
run: |
|
||||
DISABLE_CI_TEST_LOG="true" cargo test --no-default-features --features="dart"
|
||||
|
||||
- name: rustfmt rust-lib
|
||||
run: cargo fmt --all -- --check
|
||||
working-directory: frontend/rust-lib/
|
||||
|
||||
- name: clippy rust-lib
|
||||
run: cargo clippy --all-targets -- -D warnings
|
||||
working-directory: frontend/rust-lib
|
||||
|
||||
- name: "Debug: show Appflowy-Cloud container logs"
|
||||
if: failure()
|
||||
working-directory: AppFlowy-Cloud
|
||||
run: |
|
||||
docker compose logs appflowy_cloud
|
||||
|
||||
- name: Clean up Docker images
|
||||
run: |
|
||||
docker image prune -af
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
name: Tauri-CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
@@ -11,28 +12,47 @@ env:
|
||||
NODE_VERSION: "18.16.0"
|
||||
PNPM_VERSION: "8.5.0"
|
||||
RUST_TOOLCHAIN: "1.77.2"
|
||||
CARGO_MAKE_VERSION: "0.36.6"
|
||||
CI: true
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
tauri-build:
|
||||
if: github.event.pull_request.draft != true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ ubuntu-20.04 ]
|
||||
# tauri-build-self-hosted:
|
||||
# if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
# runs-on: self-hosted
|
||||
#
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4
|
||||
# - name: install frontend dependencies
|
||||
# working-directory: frontend/appflowy_web_app
|
||||
# run: |
|
||||
# mkdir dist
|
||||
# pnpm install
|
||||
# cd src-tauri && cargo build
|
||||
#
|
||||
# - name: test and lint
|
||||
# working-directory: frontend/appflowy_web_app
|
||||
# run: |
|
||||
# pnpm run lint:tauri
|
||||
#
|
||||
# - uses: tauri-apps/tauri-action@v0
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# with:
|
||||
# tauriScript: pnpm tauri
|
||||
# projectPath: frontend/appflowy_web_app
|
||||
# args: "--debug"
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
tauri-build-ubuntu:
|
||||
#if: github.event.pull_request.head.repo.full_name != github.repository
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
env:
|
||||
CI: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Maximize build space (ubuntu only)
|
||||
if: matrix.platform == 'ubuntu-20.04'
|
||||
- name: Maximize build space
|
||||
run: |
|
||||
sudo rm -rf /usr/share/dotnet
|
||||
sudo rm -rf /opt/ghc
|
||||
@@ -61,36 +81,27 @@ jobs:
|
||||
override: true
|
||||
profile: minimal
|
||||
|
||||
- name: Rust cache
|
||||
uses: swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: "./frontend/appflowy_web_app/src-tauri -> target"
|
||||
|
||||
- name: Node_modules cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: frontend/appflowy_web_app/node_modules
|
||||
key: node-modules-${{ runner.os }}
|
||||
|
||||
- name: install dependencies (windows only)
|
||||
if: matrix.platform == 'windows-latest'
|
||||
working-directory: frontend
|
||||
run: |
|
||||
cargo install --force duckscript_cli
|
||||
vcpkg integrate install
|
||||
|
||||
- name: install dependencies (ubuntu only)
|
||||
if: matrix.platform == 'ubuntu-20.04'
|
||||
- name: install dependencies
|
||||
working-directory: frontend
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
|
||||
- name: install cargo-make
|
||||
- uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-make@${{ env.CARGO_MAKE_VERSION }}
|
||||
|
||||
- name: install tauri deps tools
|
||||
working-directory: frontend
|
||||
run: |
|
||||
cargo install --force cargo-make
|
||||
cargo make appflowy-tauri-deps-tools
|
||||
shell: bash
|
||||
|
||||
- name: install frontend dependencies
|
||||
working-directory: frontend/appflowy_web_app
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
name: WEB-CI
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
build:
|
||||
description: 'Build the web app'
|
||||
required: true
|
||||
default: 'true'
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
NODE_VERSION: "18.16.0"
|
||||
PNPM_VERSION: "8.5.0"
|
||||
RUST_TOOLCHAIN: "1.77.2"
|
||||
CARGO_MAKE_VERSION: "0.36.6"
|
||||
|
||||
jobs:
|
||||
web-build:
|
||||
if: github.event.pull_request.draft != true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ ubuntu-latest ]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Cache Rust Dependencies
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: rust-dependencies-${{ runner.os }}
|
||||
workspaces: |
|
||||
frontend/rust-lib
|
||||
frontend/appflowy_web/appflowy_wasm
|
||||
|
||||
# TODO: Can combine caching deps and node_modules in one
|
||||
# See Glob patterns: https://github.com/actions/toolkit/tree/main/packages/glob
|
||||
- name: Cache Node.js dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: npm-${{ runner.os }}
|
||||
|
||||
- name: Cache node_modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: frontend/appflowy_web/node_modules
|
||||
key: node-modules-${{ runner.os }}
|
||||
|
||||
- name: Install Rust toolchain
|
||||
id: rust_toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
override: true
|
||||
profile: minimal
|
||||
|
||||
- name: Install wasm-pack
|
||||
run: cargo install wasm-pack
|
||||
|
||||
- uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-make@${{ env.CARGO_MAKE_VERSION }}
|
||||
|
||||
- name: install dependencies
|
||||
if: matrix.platform == 'ubuntu-latest'
|
||||
working-directory: frontend
|
||||
run: |
|
||||
sudo apt-get update
|
||||
npm install -g pnpm@${{ env.PNPM_VERSION }}
|
||||
cargo make install_web_protobuf
|
||||
|
||||
- name: Build
|
||||
working-directory: frontend/appflowy_web
|
||||
run: |
|
||||
pnpm install
|
||||
pnpm run build_release_wasm
|
||||
@@ -1,4 +1,91 @@
|
||||
# Release Notes
|
||||
## Version 0.6.9 - 09/09/2024
|
||||
### New Features
|
||||
- Added a new property type, 'Files & media'
|
||||
- Supported Apple Sign-in
|
||||
- Displayed the page icon next to the row name when the row page contains nested notes
|
||||
- Enabled Delete Account in Settings
|
||||
- Included a collapsible navigation menu in your published site
|
||||
|
||||
### Bug Fixes
|
||||
- Fixed the space name color issue in the community themes
|
||||
- Fixed database filters and sorting issues
|
||||
- Fixed the issue of not being able to fully display the title on Kanban cards
|
||||
- Fixed the inability to see the entire text of a checklist item when it's more than one line long
|
||||
- Fixed hide/unhide buttons in the No Status group
|
||||
- Fixed the inability to edit group names on Kanban boards
|
||||
- Made error codes more user-friendly
|
||||
- Added leading zeros to day and month in date format
|
||||
|
||||
## Version 0.6.8 - 22/08/2024
|
||||
### New Features
|
||||
- Enabled viewing data inside a database record on mobile.
|
||||
- Added the ability to invite members to a workspace on mobile.
|
||||
- Introduced Ask AI in the Home tab on mobile.
|
||||
- Import CSV files with up to 1,000 rows.
|
||||
- Convert properties from one type to another while preserving the data.
|
||||
- Optimized the speed of opening documents and databases.
|
||||
- Improved syncing performance across devices.
|
||||
- Added support for a monochrome app icon on Android.
|
||||
|
||||
### Bug Fixes
|
||||
- Removed the Wayland header from the AppImage build.
|
||||
- Fixed the issue where pasting a web image on mobile failed.
|
||||
- Corrected the Local AI state when switching between different workspaces.
|
||||
- Fixed high CPU usage when opening large databases.
|
||||
|
||||
## Version 0.6.7 - 13/08/2024
|
||||
### New Features
|
||||
- Redesigned the icon picker design on Desktop.
|
||||
- Redesigned the notification page on Mobile.
|
||||
|
||||
### Bug Fixes
|
||||
- Enhance the toolbar tooltip functionality on Desktop.
|
||||
- Enhance the slash menu user experience on Desktop.
|
||||
- Fixed the issue where list style overrides occurred during text pasting.
|
||||
- Fixed the issue where linking multiple databases in the same document could cause random loss of focus.
|
||||
|
||||
## Version 0.6.6 - 30/07/2024
|
||||
### New Features
|
||||
- Upgrade your workspace to a premium plan to unlock more features and storage.
|
||||
- Image galleries and drag-and-drop image support in documents.
|
||||
|
||||
### Bug Fixes
|
||||
- Fix minor UI issues on Desktop and Mobile.
|
||||
|
||||
## Version 0.6.5 - 24/07/2024
|
||||
### New Features
|
||||
- Publish a Database to the Web
|
||||
|
||||
## Version 0.6.4 - 16/07/2024
|
||||
### New Features
|
||||
- Enhanced the message style on the AI chat page.
|
||||
- Added the ability to choose cursor color and selection color from a palette in settings page.
|
||||
### Bug Fixes
|
||||
- Optimized the performance for loading recent pages.
|
||||
- Fixed an issue where the cursor would jump randomly when typing in the document title on mobile.
|
||||
|
||||
## Version 0.6.3 - 08/07/2024
|
||||
### New Features
|
||||
- Publish a Document to the Web
|
||||
|
||||
## Version 0.6.2 - 01/07/2024
|
||||
### New Features
|
||||
- Added support for duplicating spaces.
|
||||
- Added support for moving pages across spaces.
|
||||
- Undo markdown formatting with `Ctrl + Z` or `Cmd + Z`.
|
||||
- Improved shortcuts settings UI.
|
||||
### Bug Fixes
|
||||
- Fixed unable to zoom in with `Ctrl` and `+` or `Cmd` and `+` on some keyboards.
|
||||
- Fixed unable to paste nested lists in existing lists.
|
||||
|
||||
## Version 0.6.1 - 22/06/2024
|
||||
### New Features
|
||||
- Introduced the "Space" feature to help you organize your pages more efficiently.
|
||||
### Bug Fixes
|
||||
- Resolved shortcut conflicts on the board page.
|
||||
- Resolved an issue where underscores could cause the editor to freeze.
|
||||
|
||||
## Version 0.6.0 - 19/06/2024
|
||||
### New Features
|
||||
- Introduced the "Space" feature to help you organize your pages more efficiently.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
You are in charge of your data and customizations.
|
||||
AppFlowy is the AI workspace where you achieve more without losing control of your data
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -19,20 +19,34 @@ You are in charge of your data and customizations.
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.appflowy.io"><b>Website</b></a> •
|
||||
<a href="https://forum.appflowy.io/"><b>Forum</b></a> •
|
||||
<a href="https://discord.gg/9Q2xaN37tV"><b>Discord</b></a> •
|
||||
<a href="https://www.reddit.com/r/AppFlowy"><b>Reddit</b></a> •
|
||||
<a href="https://twitter.com/appflowy"><b>Twitter</b></a>
|
||||
</p>
|
||||
|
||||
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/236664610-fc209a97-815e-4716-af07-d94a859d1907.png" alt="AppFlowy Docs & Notes & Wikis" width="1000px" /></p>
|
||||
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/236664628-5def2450-914a-4b2d-b907-92b7476b9863.png" alt="AppFlowy Databases for Tasks and Projects" width="1000px" /></p>
|
||||
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/236664642-22e26c1b-5eae-4635-9aa6-b12ecf1c3c46.png" alt="AppFlowy Kanban Board for To-Dos" width="1000px" /></p>
|
||||
<p align="center"><img src="https://github.com/AppFlowy-IO/AppFlowy/assets/12026239/6be93d2b-a5c5-48a9-b7cf-c599d5f5140c" alt="AppFlowy Calendars for Plan and Manage Content" width="1000px" /></p>
|
||||
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/236664657-dc5291f3-67b0-4a43-a818-640e92735deb.png" alt="AppFlowy OpenAI GPT Writers" width="1000px" /></p>
|
||||
<p align="center"><img src="https://appflowy.io/_next/static/media/tasks.796c753e.png" alt="AppFlowy Kanban Board for To-dos" /></p>
|
||||
<p align="center"><img src="https://appflowy.io/_next/static/media/Grid.9e30484b.png" alt="AppFlowy Databases for Tasks and Projects" /></p>
|
||||
<p align="center"><img src="https://appflowy.io/_next/static/media/sites.a8d5b2b9.png" alt="AppFlowy Sites for Beautiful documentation" /></p>
|
||||
<p align="center"><img src="https://appflowy.io/_next/static/media/ai.e1460982.png" alt="AppFlowy AI" /></p>
|
||||
<p align="center"><img src="https://appflowy.io/_next/static/media/template.9ea13c3b.png" alt="AppFlowy Templates" /></p>
|
||||
|
||||
<br></br>
|
||||
<p align="center" >
|
||||
<img src="https://github.com/user-attachments/assets/5841c491-b564-4a26-b9b6-191def430911" alt="Work across devices" width="1040px" /></p>
|
||||
<p align="center" >
|
||||
<img src="https://github.com/user-attachments/assets/c2ba6bb8-746c-4743-9393-d008a669be95" alt="Work across devices" width="1040px" /></p>
|
||||
<p align="center" >
|
||||
<img src="https://github.com/user-attachments/assets/e83dd1a3-4975-4d0e-91a1-9eb6e0d248cd" alt="Work across devices" width="1040px" /></p>
|
||||
|
||||
## User Installation
|
||||
|
||||
- [Windows/Mac/Linux](https://docs.appflowy.io/docs/appflowy/install-appflowy/installation-methods/mac-windows-linux-packages)
|
||||
- [Docker](https://docs.appflowy.io/docs/appflowy/install-appflowy/installation-methods/installing-with-docker)
|
||||
- [Download AppFlowy Desktop (macOS, Windows, and Linux)](https://github.com/AppFlowy-IO/AppFlowy/releases)
|
||||
- Other channels: [FlatHub](https://flathub.org/apps/io.appflowy.AppFlowy), [Snapcraft](https://snapcraft.io/appflowy), [Sourceforge](https://sourceforge.net/projects/appflowy/)
|
||||
- Available on
|
||||
- App Store: iPhone
|
||||
- Play Store: Android 10 or above; ARMv7 is not supported
|
||||
- [Self-hosting AppFlowy](https://docs.appflowy.io/docs/guides/appflowy/self-hosting-appflowy)
|
||||
- [Source](https://docs.appflowy.io/docs/documentation/appflowy/from-source)
|
||||
|
||||
## Built With
|
||||
|
||||
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 115 KiB |
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 77 KiB |
@@ -13,7 +13,6 @@
|
||||
"type": "dart",
|
||||
"env": {
|
||||
"RUST_LOG": "debug",
|
||||
"RUST_BACKTRACE": "1"
|
||||
},
|
||||
// uncomment the following line to testing performance.
|
||||
// "flutterMode": "profile",
|
||||
@@ -138,4 +137,4 @@
|
||||
"cwd": "${workspaceRoot}/appflowy_tauri/"
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
|
||||
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
|
||||
CARGO_MAKE_CRATE_NAME = "dart-ffi"
|
||||
LIB_NAME = "dart_ffi"
|
||||
APPFLOWY_VERSION = "0.6.0"
|
||||
APPFLOWY_VERSION = "0.6.9"
|
||||
FLUTTER_DESKTOP_FEATURES = "dart"
|
||||
PRODUCT_NAME = "AppFlowy"
|
||||
MACOSX_DEPLOYMENT_TARGET = "11.0"
|
||||
@@ -50,7 +50,6 @@ 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"
|
||||
TAURI_APP_BACKEND_SERVICE_PATH = "appflowy_web_app/src/application/services/tauri-services/backend"
|
||||
# Test default config
|
||||
TEST_CRATE_TYPE = "cdylib"
|
||||
|
||||
@@ -52,8 +52,8 @@ android {
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "io.appflowy.appflowy"
|
||||
minSdkVersion 24
|
||||
targetSdkVersion 33
|
||||
minSdkVersion 29
|
||||
targetSdkVersion 34
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
multiDexEnabled true
|
||||
@@ -87,6 +87,13 @@ android {
|
||||
path "src/main/CMakeLists.txt"
|
||||
}
|
||||
}
|
||||
|
||||
// only support arm64-v8a
|
||||
defaultConfig {
|
||||
ndk {
|
||||
abiFilters "arm64-v8a"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
|
||||
|
After Width: | Height: | Size: 44 KiB |
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/black" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background" />
|
||||
<foreground
|
||||
android:drawable="@mipmap/ic_launcher_foreground" />
|
||||
<monochrome
|
||||
android:drawable="@mipmap/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#FFFFFF</color>
|
||||
</resources>
|
||||
@@ -0,0 +1 @@
|
||||
{ "artificial_intelligence": [ { "name": "ai-chip-spark", "keywords": [ "chip", "processor", "artificial", "intelligence", "ai" ], "content": "<svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<g clip-path=\"url(#clip0_1068_189544)\">\n<path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M8.71468 0.0400391C9.1289 0.0400391 9.46468 0.375825 9.46468 0.790039V1.97754H10.4999C11.3283 1.97754 11.9999 2.64911 11.9999 3.47754V4.51367H13.2186C13.6328 4.51367 13.9686 4.84946 13.9686 5.26367C13.9686 5.67789 13.6328 6.01367 13.2186 6.01367H11.9999V7.94324H13.2186C13.6328 7.94324 13.9686 8.27902 13.9686 8.69324C13.9686 9.10745 13.6328 9.44324 13.2186 9.44324H11.9999V10.4775C11.9999 11.306 11.3283 11.9775 10.4999 11.9775H9.46468V13.2099C9.46468 13.6241 9.1289 13.9599 8.71468 13.9599C8.30047 13.9599 7.96468 13.6241 7.96468 13.2099V11.9775H6.03524V13.2099C6.03524 13.6241 5.69945 13.9599 5.28524 13.9599C4.87103 13.9599 4.53524 13.6241 4.53524 13.2099V11.9775H3.49984C2.67141 11.9775 1.99984 11.306 1.99984 10.4775V9.44324H0.783203C0.368989 9.44324 0.0332031 9.10745 0.0332031 8.69324C0.0332031 8.27902 0.368989 7.94324 0.783203 7.94324H1.99984V6.01367H0.783203C0.368989 6.01367 0.0332031 5.67789 0.0332031 5.26367C0.0332031 4.84946 0.368989 4.51367 0.783203 4.51367H1.99984V3.47754C1.99984 2.64911 2.67141 1.97754 3.49984 1.97754H4.53524V0.790039C4.53524 0.375825 4.87103 0.0400391 5.28524 0.0400391C5.69945 0.0400391 6.03524 0.375825 6.03524 0.790039V1.97754H7.96468V0.790039C7.96468 0.375825 8.30047 0.0400391 8.71468 0.0400391ZM6.24357 4.5682C6.41951 3.76446 7.56392 3.75946 7.74688 4.56163L7.75499 4.59732C7.76038 4.62113 7.76543 4.64342 7.7708 4.66628C7.96264 5.48335 8.62389 6.10768 9.45164 6.25169C10.2908 6.39768 10.2908 7.60232 9.45164 7.74831C8.61948 7.89308 7.95559 8.52332 7.76776 9.34682L7.74688 9.43837C7.56392 10.2405 6.41951 10.2355 6.24357 9.4318L6.22638 9.35325C6.04547 8.52684 5.38257 7.89201 4.54911 7.74701C3.71142 7.60128 3.71141 6.39872 4.54911 6.25299C5.37967 6.10849 6.04086 5.47757 6.22447 4.65539L6.23707 4.598L6.24357 4.5682Z\" fill=\"black\"/>\n</g>\n<defs>\n<clipPath id=\"clip0_1068_189544\">\n<rect width=\"14\" height=\"14\" fill=\"white\"/>\n</clipPath>\n</defs>\n</svg>\n" }, { "name": "ai-cloud-spark", "keywords": [ "cloud", "internet", "server", "network", "artificial", "intelligence", "ai" ], "content": "<svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <g clip-path=\"url(#clip0_1068_189529)\">\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\"\n d=\"M7.88381 0.165291C6.89196 0.0450664 5.88886 0.268505 5.04181 0.798345C4.25349 1.29145 3.64467 2.02219 3.30135 2.88247C2.87262 2.92977 2.45508 3.05187 2.06781 3.24362C1.63324 3.45878 1.24531 3.75744 0.92616 4.12253C0.281611 4.85986 -0.0436335 5.82304 0.0219743 6.80017C0.087582 7.77731 0.538668 8.68836 1.276 9.33291C1.52925 9.55429 1.80913 9.738 2.10708 9.88114C2.23853 9.09496 2.79956 8.38069 3.79016 8.20836C4.35848 8.10949 4.81104 7.67811 4.93736 7.11585L4.96023 7.01146C5.43591 4.83845 8.53 4.82492 9.02466 6.9937L9.03475 7.03814C9.04166 7.06865 9.047 7.09221 9.05244 7.11538C9.18449 7.6778 9.63973 8.10763 10.2096 8.20676C11.2609 8.38966 11.8287 9.18251 11.9131 10.0243C12.2792 9.884 12.6197 9.67707 12.9162 9.41068C13.5042 8.88231 13.878 8.15668 13.9669 7.37119C14.0558 6.58569 13.8536 5.79486 13.3985 5.14845C12.9811 4.55545 12.3775 4.12175 11.6866 3.91417C11.5613 2.99088 11.1436 2.12994 10.4923 1.45901C9.79633 0.742138 8.87567 0.285516 7.88381 0.165291ZM6.18132 7.27876C6.37146 6.41016 7.60823 6.40476 7.80595 7.27166L7.81577 7.3149C7.8225 7.34462 7.82879 7.37239 7.83553 7.4011C8.08204 8.451 8.93171 9.25323 9.99531 9.43826C10.9004 9.59573 10.9004 10.8951 9.99531 11.0525C8.92604 11.2386 8.07299 12.0484 7.83164 13.1065L7.80595 13.2191C7.60823 14.086 6.37146 14.0806 6.18132 13.212L6.16017 13.1154C5.92765 12.0532 5.07564 11.2373 4.0044 11.0509C3.10108 10.8938 3.10107 9.59701 4.0044 9.43986C5.07191 9.25414 5.92172 8.44323 6.15771 7.38648L6.17343 7.31494L6.18132 7.27876Z\"\n fill=\"black\"/>\n </g>\n <defs>\n <clipPath id=\"clip0_1068_189529\">\n <rect width=\"14\" height=\"14\" fill=\"white\"/>\n </clipPath>\n </defs>\n</svg>\n" }, { "name": "ai-edit-spark", "keywords": [ "change", "edit", "modify", "pencil", "write", "writing", "artificial", "intelligence", "ai" ], "content": "<svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<g clip-path=\"url(#clip0_1068_189535)\">\n<path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M2.85637 0.654372C3.04651 -0.214226 4.28328 -0.219632 4.481 0.647271L4.49082 0.690505C4.49756 0.720231 4.50384 0.747995 4.51058 0.776709C4.75709 1.82661 5.60676 2.62884 6.67036 2.81387C7.57548 2.97134 7.57548 4.27068 6.67036 4.42815C5.60109 4.61417 4.74804 5.42398 4.50669 6.48214L4.481 6.59475C4.28328 7.46166 3.04651Line truncated
|
||||
@@ -0,0 +1,12 @@
|
||||
# dart_dependency_validator.yaml
|
||||
|
||||
allow_pins: true
|
||||
|
||||
include:
|
||||
- "lib/**"
|
||||
|
||||
exclude:
|
||||
- "packages/**"
|
||||
|
||||
ignore:
|
||||
- analyzer
|
||||
@@ -3,8 +3,6 @@
|
||||
import 'dart:io';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
@@ -12,6 +10,7 @@ import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/account/account.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
|
||||
@@ -19,6 +18,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text_field.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';
|
||||
@@ -42,7 +42,7 @@ void main() {
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
// reanme the name of the anon user
|
||||
// rename the name of the anon user
|
||||
await tester.openSettings();
|
||||
await tester.openSettingsPage(SettingsPage.account);
|
||||
await tester.pumpAndSettle();
|
||||
@@ -51,12 +51,12 @@ void main() {
|
||||
|
||||
// Scroll to sign-in
|
||||
await tester.scrollUntilVisible(
|
||||
find.byType(SignInOutButton),
|
||||
find.byType(AccountSignInOutButton),
|
||||
100,
|
||||
scrollable: find.findSettingsScrollable(),
|
||||
);
|
||||
|
||||
await tester.tapButton(find.byType(SignInOutButton));
|
||||
await tester.tapButton(find.byType(AccountSignInOutButton));
|
||||
|
||||
// sign up with Google
|
||||
await tester.tapGoogleLoginInButton();
|
||||
@@ -68,7 +68,7 @@ void main() {
|
||||
|
||||
// Scroll to sign-out
|
||||
await tester.scrollUntilVisible(
|
||||
find.byType(SignInOutButton),
|
||||
find.byType(AccountSignInOutButton),
|
||||
100,
|
||||
scrollable: find.findSettingsScrollable(),
|
||||
);
|
||||
@@ -85,7 +85,7 @@ void main() {
|
||||
await tester.openSettings();
|
||||
await tester.openSettingsPage(SettingsPage.account);
|
||||
final userNameInput =
|
||||
tester.widget(find.byType(UserProfileSetting)) as UserProfileSetting;
|
||||
tester.widget(find.byType(AccountUserProfile)) as AccountUserProfile;
|
||||
expect(userNameInput.name, 'Me');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/account/account.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@@ -41,17 +42,17 @@ void main() {
|
||||
|
||||
// Scroll to sign-out
|
||||
await tester.scrollUntilVisible(
|
||||
find.byType(SignInOutButton),
|
||||
find.byType(AccountSignInOutButton),
|
||||
100,
|
||||
scrollable: find.findSettingsScrollable(),
|
||||
);
|
||||
await tester.tapButton(find.byType(SignInOutButton));
|
||||
await tester.tapButton(find.byType(AccountSignInOutButton));
|
||||
|
||||
tester.expectToSeeText(LocaleKeys.button_confirm.tr());
|
||||
await tester.tapButtonWithName(LocaleKeys.button_confirm.tr());
|
||||
tester.expectToSeeText(LocaleKeys.button_ok.tr());
|
||||
await tester.tapButtonWithName(LocaleKeys.button_ok.tr());
|
||||
|
||||
// Go to the sign in page again
|
||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
await tester.pumpAndSettle(const Duration(seconds: 5));
|
||||
tester.expectToSeeGoogleLoginButton();
|
||||
});
|
||||
|
||||
@@ -67,11 +68,11 @@ void main() {
|
||||
|
||||
// Scroll to sign-in
|
||||
await tester.scrollUntilVisible(
|
||||
find.byType(SignInOutButton),
|
||||
find.byType(AccountSignInOutButton),
|
||||
100,
|
||||
scrollable: find.findSettingsScrollable(),
|
||||
);
|
||||
await tester.tapButton(find.byType(SignInOutButton));
|
||||
await tester.tapButton(find.byType(AccountSignInOutButton));
|
||||
|
||||
tester.expectToSeeGoogleLoginButton();
|
||||
});
|
||||
|
||||
@@ -46,7 +46,6 @@ void main() {
|
||||
await tester.ime.insertText(inputContent);
|
||||
expect(find.text(inputContent, findRichText: true), findsOneWidget);
|
||||
|
||||
// TODO(nathan): remove the await
|
||||
// 6 seconds for data sync
|
||||
await tester.waitForSeconds(6);
|
||||
|
||||
|
||||
@@ -1,93 +1,93 @@
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_supabase_cloud.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
// import 'package:appflowy/env/cloud_env.dart';
|
||||
// import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
// import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.dart';
|
||||
// import 'package:appflowy/workspace/presentation/settings/widgets/setting_supabase_cloud.dart';
|
||||
// import 'package:flutter_test/flutter_test.dart';
|
||||
// import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../shared/util.dart';
|
||||
// import '../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
// void main() {
|
||||
// IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('supabase auth', () {
|
||||
testWidgets('sign in with supabase', (tester) async {
|
||||
await tester.initializeAppFlowy(cloudType: AuthenticatorType.supabase);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
});
|
||||
// group('supabase auth', () {
|
||||
// testWidgets('sign in with supabase', (tester) async {
|
||||
// await tester.initializeAppFlowy(cloudType: AuthenticatorType.supabase);
|
||||
// await tester.tapGoogleLoginInButton();
|
||||
// await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
// });
|
||||
|
||||
testWidgets('sign out with supabase', (tester) async {
|
||||
await tester.initializeAppFlowy(cloudType: AuthenticatorType.supabase);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
// testWidgets('sign out with supabase', (tester) async {
|
||||
// await tester.initializeAppFlowy(cloudType: AuthenticatorType.supabase);
|
||||
// await tester.tapGoogleLoginInButton();
|
||||
|
||||
// Open the setting page and sign out
|
||||
await tester.openSettings();
|
||||
await tester.openSettingsPage(SettingsPage.account);
|
||||
await tester.logout();
|
||||
// // Open the setting page and sign out
|
||||
// await tester.openSettings();
|
||||
// await tester.openSettingsPage(SettingsPage.account);
|
||||
// await tester.logout();
|
||||
|
||||
// Go to the sign in page again
|
||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
tester.expectToSeeGoogleLoginButton();
|
||||
});
|
||||
// // Go to the sign in page again
|
||||
// await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
// tester.expectToSeeGoogleLoginButton();
|
||||
// });
|
||||
|
||||
testWidgets('sign in as anonymous', (tester) async {
|
||||
await tester.initializeAppFlowy(cloudType: AuthenticatorType.supabase);
|
||||
await tester.tapSignInAsGuest();
|
||||
// testWidgets('sign in as anonymous', (tester) async {
|
||||
// await tester.initializeAppFlowy(cloudType: AuthenticatorType.supabase);
|
||||
// await tester.tapSignInAsGuest();
|
||||
|
||||
// should not see the sync setting page when sign in as anonymous
|
||||
await tester.openSettings();
|
||||
await tester.openSettingsPage(SettingsPage.account);
|
||||
// // should not see the sync setting page when sign in as anonymous
|
||||
// await tester.openSettings();
|
||||
// await tester.openSettingsPage(SettingsPage.account);
|
||||
|
||||
// Scroll to sign-out
|
||||
await tester.scrollUntilVisible(
|
||||
find.byType(SignInOutButton),
|
||||
100,
|
||||
scrollable: find.findSettingsScrollable(),
|
||||
);
|
||||
await tester.tapButton(find.byType(SignInOutButton));
|
||||
// // Scroll to sign-out
|
||||
// await tester.scrollUntilVisible(
|
||||
// find.byType(SignInOutButton),
|
||||
// 100,
|
||||
// scrollable: find.findSettingsScrollable(),
|
||||
// );
|
||||
// await tester.tapButton(find.byType(SignInOutButton));
|
||||
|
||||
tester.expectToSeeGoogleLoginButton();
|
||||
});
|
||||
// tester.expectToSeeGoogleLoginButton();
|
||||
// });
|
||||
|
||||
// testWidgets('enable encryption', (tester) async {
|
||||
// await tester.initializeAppFlowy(cloudType: CloudType.supabase);
|
||||
// await tester.tapGoogleLoginInButton();
|
||||
// // testWidgets('enable encryption', (tester) async {
|
||||
// // await tester.initializeAppFlowy(cloudType: CloudType.supabase);
|
||||
// // await tester.tapGoogleLoginInButton();
|
||||
|
||||
// // Open the setting page and sign out
|
||||
// await tester.openSettings();
|
||||
// await tester.openSettingsPage(SettingsPage.cloud);
|
||||
// // // Open the setting page and sign out
|
||||
// // await tester.openSettings();
|
||||
// // await tester.openSettingsPage(SettingsPage.cloud);
|
||||
|
||||
// // the switch should be off by default
|
||||
// tester.assertEnableEncryptSwitchValue(false);
|
||||
// await tester.toggleEnableEncrypt();
|
||||
// // // the switch should be off by default
|
||||
// // tester.assertEnableEncryptSwitchValue(false);
|
||||
// // await tester.toggleEnableEncrypt();
|
||||
|
||||
// // the switch should be on after toggling
|
||||
// tester.assertEnableEncryptSwitchValue(true);
|
||||
// // // the switch should be on after toggling
|
||||
// // tester.assertEnableEncryptSwitchValue(true);
|
||||
|
||||
// // the switch can not be toggled back to off
|
||||
// await tester.toggleEnableEncrypt();
|
||||
// tester.assertEnableEncryptSwitchValue(true);
|
||||
// });
|
||||
// // // the switch can not be toggled back to off
|
||||
// // await tester.toggleEnableEncrypt();
|
||||
// // tester.assertEnableEncryptSwitchValue(true);
|
||||
// // });
|
||||
|
||||
testWidgets('enable sync', (tester) async {
|
||||
await tester.initializeAppFlowy(cloudType: AuthenticatorType.supabase);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
// testWidgets('enable sync', (tester) async {
|
||||
// await tester.initializeAppFlowy(cloudType: AuthenticatorType.supabase);
|
||||
// await tester.tapGoogleLoginInButton();
|
||||
|
||||
// Open the setting page and sign out
|
||||
await tester.openSettings();
|
||||
await tester.openSettingsPage(SettingsPage.cloud);
|
||||
// // Open the setting page and sign out
|
||||
// await tester.openSettings();
|
||||
// await tester.openSettingsPage(SettingsPage.cloud);
|
||||
|
||||
// the switch should be on by default
|
||||
tester.assertSupabaseEnableSyncSwitchValue(true);
|
||||
await tester.toggleEnableSync(SupabaseEnableSync);
|
||||
// // the switch should be on by default
|
||||
// tester.assertSupabaseEnableSyncSwitchValue(true);
|
||||
// await tester.toggleEnableSync(SupabaseEnableSync);
|
||||
|
||||
// the switch should be off
|
||||
tester.assertSupabaseEnableSyncSwitchValue(false);
|
||||
// // the switch should be off
|
||||
// tester.assertSupabaseEnableSyncSwitchValue(false);
|
||||
|
||||
// the switch should be on after toggling
|
||||
await tester.toggleEnableSync(SupabaseEnableSync);
|
||||
tester.assertSupabaseEnableSyncSwitchValue(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
// // the switch should be on after toggling
|
||||
// await tester.toggleEnableSync(SupabaseEnableSync);
|
||||
// tester.assertSupabaseEnableSyncSwitchValue(true);
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
@@ -11,6 +9,7 @@ import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/account/account_user_profile.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/user_avatar.dart';
|
||||
@@ -18,6 +17,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text_field.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
@@ -47,13 +47,12 @@ void main() {
|
||||
await tester.openSettingsPage(SettingsPage.account);
|
||||
|
||||
await tester.enterUserName(name);
|
||||
await tester.tapEscButton();
|
||||
await tester.pumpAndSettle(const Duration(seconds: 6));
|
||||
await tester.logout();
|
||||
|
||||
// wait 2 seconds for the sync to finish
|
||||
await tester.pumpAndSettle(const Duration(seconds: 2));
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('get user icon and name from server', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
@@ -68,7 +67,7 @@ void main() {
|
||||
|
||||
// Verify name
|
||||
final profileSetting =
|
||||
tester.widget(find.byType(UserProfileSetting)) as UserProfileSetting;
|
||||
tester.widget(find.byType(AccountUserProfile)) as AccountUserProfile;
|
||||
|
||||
expect(profileSetting.name, name);
|
||||
});
|
||||
|
||||
@@ -22,7 +22,7 @@ void main() {
|
||||
const fieldName = "test change field";
|
||||
await tester.createField(
|
||||
FieldType.RichText,
|
||||
fieldName,
|
||||
name: fieldName,
|
||||
layout: ViewLayoutPB.Board,
|
||||
);
|
||||
await tester.tapButton(card1);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
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_test/flutter_test.dart';
|
||||
@@ -41,7 +42,7 @@ void main() {
|
||||
name: 'my grid',
|
||||
layout: ViewLayoutPB.Grid,
|
||||
);
|
||||
await tester.createField(FieldType.RichText, 'description');
|
||||
await tester.createField(FieldType.RichText, name: 'description');
|
||||
|
||||
await tester.editCell(
|
||||
rowIndex: 0,
|
||||
@@ -81,7 +82,7 @@ void main() {
|
||||
const fieldType = FieldType.Number;
|
||||
|
||||
// Create a number field
|
||||
await tester.createField(fieldType, fieldType.name);
|
||||
await tester.createField(fieldType);
|
||||
|
||||
await tester.editCell(
|
||||
rowIndex: 0,
|
||||
@@ -157,7 +158,7 @@ void main() {
|
||||
const fieldType = FieldType.CreatedTime;
|
||||
// Create a create time field
|
||||
// The create time field is not editable
|
||||
await tester.createField(fieldType, fieldType.name);
|
||||
await tester.createField(fieldType);
|
||||
|
||||
await tester.tapCellInGrid(rowIndex: 0, fieldType: fieldType);
|
||||
|
||||
@@ -175,7 +176,7 @@ void main() {
|
||||
const fieldType = FieldType.LastEditedTime;
|
||||
// Create a last time field
|
||||
// The last time field is not editable
|
||||
await tester.createField(fieldType, fieldType.name);
|
||||
await tester.createField(fieldType);
|
||||
|
||||
await tester.tapCellInGrid(rowIndex: 0, fieldType: fieldType);
|
||||
|
||||
@@ -191,7 +192,7 @@ void main() {
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
|
||||
const fieldType = FieldType.DateTime;
|
||||
await tester.createField(fieldType, fieldType.name);
|
||||
await tester.createField(fieldType);
|
||||
|
||||
// Tap the cell to invoke the field editor
|
||||
await tester.tapCellInGrid(rowIndex: 0, fieldType: fieldType);
|
||||
@@ -366,7 +367,7 @@ void main() {
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
|
||||
const fieldType = FieldType.MultiSelect;
|
||||
await tester.createField(fieldType, fieldType.name);
|
||||
await tester.createField(fieldType, name: fieldType.i18n);
|
||||
|
||||
// Tap the cell to invoke the selection option editor
|
||||
await tester.tapSelectOptionCellInGrid(rowIndex: 0, fieldType: fieldType);
|
||||
@@ -449,7 +450,7 @@ void main() {
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
|
||||
const fieldType = FieldType.Checklist;
|
||||
await tester.createField(fieldType, fieldType.name);
|
||||
await tester.createField(fieldType);
|
||||
|
||||
// assert that there is no progress bar in the grid
|
||||
tester.assertChecklistCellInGrid(rowIndex: 0, percent: null);
|
||||
@@ -461,22 +462,22 @@ void main() {
|
||||
tester.assertChecklistEditorVisible(visible: true);
|
||||
|
||||
// create a new task with enter
|
||||
await tester.createNewChecklistTask(name: "task 0", enter: true);
|
||||
await tester.createNewChecklistTask(name: "task 1", enter: true);
|
||||
|
||||
// assert that the task is displayed
|
||||
tester.assertChecklistTaskInEditor(
|
||||
index: 0,
|
||||
name: "task 0",
|
||||
name: "task 1",
|
||||
isChecked: false,
|
||||
);
|
||||
|
||||
// update the task's name
|
||||
await tester.renameChecklistTask(index: 0, name: "task 1");
|
||||
await tester.renameChecklistTask(index: 0, name: "task 11");
|
||||
|
||||
// assert that the task's name is updated
|
||||
tester.assertChecklistTaskInEditor(
|
||||
index: 0,
|
||||
name: "task 1",
|
||||
name: "task 11",
|
||||
isChecked: false,
|
||||
);
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/field/type_option_editor/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:easy_localization/easy_localization.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 '../../shared/database_test_op.dart';
|
||||
import '../../shared/util.dart';
|
||||
@@ -56,11 +57,22 @@ void main() {
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
|
||||
// create a field
|
||||
await tester.createField(FieldType.Checklist, 'checklist');
|
||||
await tester.createField(FieldType.Checklist);
|
||||
tester.findFieldWithName(FieldType.Checklist.i18n);
|
||||
|
||||
// check the field is created successfully
|
||||
tester.findFieldWithName('checklist');
|
||||
await tester.pumpAndSettle();
|
||||
// editing field type during field creation should change title
|
||||
await tester.createField(FieldType.MultiSelect);
|
||||
tester.findFieldWithName(FieldType.MultiSelect.i18n);
|
||||
|
||||
// not if the user changes the title manually though
|
||||
const name = "New field";
|
||||
await tester.createField(FieldType.DateTime);
|
||||
await tester.tapGridFieldWithName(FieldType.DateTime.i18n);
|
||||
await tester.renameField(name);
|
||||
await tester.tapEditFieldButton();
|
||||
await tester.tapSwitchFieldTypeButton();
|
||||
await tester.selectFieldType(FieldType.URL);
|
||||
tester.findFieldWithName(name);
|
||||
});
|
||||
|
||||
testWidgets('delete field', (tester) async {
|
||||
@@ -70,14 +82,14 @@ void main() {
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
|
||||
// create a field
|
||||
await tester.createField(FieldType.Checkbox, 'New field 1');
|
||||
await tester.createField(FieldType.Checkbox, name: 'New field 1');
|
||||
|
||||
// Delete the field
|
||||
await tester.tapGridFieldWithName('New field 1');
|
||||
await tester.tapDeletePropertyButton();
|
||||
|
||||
// confirm delete
|
||||
await tester.tapDialogOkButton();
|
||||
await tester.tapButtonWithName(LocaleKeys.space_delete.tr());
|
||||
|
||||
tester.noFieldWithName('New field 1');
|
||||
await tester.pumpAndSettle();
|
||||
@@ -90,10 +102,7 @@ void main() {
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
|
||||
// create a field
|
||||
await tester.scrollToRight(find.byType(GridPage));
|
||||
await tester.tapNewPropertyButton();
|
||||
await tester.renameField('New field 1');
|
||||
await tester.dismissFieldEditor();
|
||||
await tester.createField(FieldType.RichText, name: 'New field 1');
|
||||
|
||||
// duplicate the field
|
||||
await tester.tapGridFieldWithName('New field 1');
|
||||
@@ -126,26 +135,6 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
testWidgets('create checklist field', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
|
||||
await tester.scrollToRight(find.byType(GridPage));
|
||||
await tester.tapNewPropertyButton();
|
||||
|
||||
// Open the type option menu
|
||||
await tester.tapSwitchFieldTypeButton();
|
||||
|
||||
await tester.selectFieldType(FieldType.Checklist);
|
||||
|
||||
// After update the field type, the cells should be updated
|
||||
await tester.findCellByFieldType(FieldType.Checklist);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
testWidgets('create list of fields', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
@@ -162,18 +151,10 @@ void main() {
|
||||
FieldType.CreatedTime,
|
||||
FieldType.Checkbox,
|
||||
]) {
|
||||
await tester.scrollToRight(find.byType(GridPage));
|
||||
await tester.tapNewPropertyButton();
|
||||
await tester.renameField(fieldType.name);
|
||||
|
||||
// Open the type option menu
|
||||
await tester.tapSwitchFieldTypeButton();
|
||||
|
||||
await tester.selectFieldType(fieldType);
|
||||
await tester.dismissFieldEditor();
|
||||
await tester.createField(fieldType);
|
||||
|
||||
// After update the field type, the cells should be updated
|
||||
await tester.findCellByFieldType(fieldType);
|
||||
tester.findCellByFieldType(fieldType);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
});
|
||||
@@ -190,15 +171,7 @@ void main() {
|
||||
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();
|
||||
await tester.createField(fieldType);
|
||||
|
||||
// open the field editor
|
||||
await tester.tapGridFieldWithName(fieldType.i18n);
|
||||
@@ -218,11 +191,7 @@ void main() {
|
||||
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();
|
||||
await tester.createField(FieldType.Number);
|
||||
|
||||
// enter some data into the first number cell
|
||||
await tester.editCell(
|
||||
@@ -243,7 +212,7 @@ void main() {
|
||||
);
|
||||
|
||||
// open editor and change number format
|
||||
await tester.tapGridFieldWithName('Number');
|
||||
await tester.tapGridFieldWithName(FieldType.Number.i18n);
|
||||
await tester.tapEditFieldButton();
|
||||
await tester.changeNumberFieldFormat();
|
||||
await tester.dismissFieldEditor();
|
||||
@@ -292,11 +261,7 @@ void main() {
|
||||
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();
|
||||
await tester.createField(FieldType.DateTime);
|
||||
|
||||
// edit the first date cell
|
||||
await tester.tapCellInGrid(rowIndex: 0, fieldType: FieldType.DateTime);
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
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/database/widgets/cell_editor/media_cell_editor.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.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';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import '../../shared/database_test_op.dart';
|
||||
import '../../shared/mock/mock_file_picker.dart';
|
||||
import '../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('media type option in database', () {
|
||||
testWidgets('add media field and add files', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
|
||||
// Invoke the field editor
|
||||
await tester.tapGridFieldWithName('Type');
|
||||
await tester.tapEditFieldButton();
|
||||
|
||||
// Change to media type
|
||||
await tester.tapSwitchFieldTypeButton();
|
||||
await tester.selectFieldType(FieldType.Media);
|
||||
await tester.dismissFieldEditor();
|
||||
|
||||
// Open media cell editor
|
||||
await tester.tapCellInGrid(rowIndex: 0, fieldType: FieldType.Media);
|
||||
await tester.findMediaCellEditor(findsOneWidget);
|
||||
|
||||
// Prepare files for upload from local
|
||||
final firstImage =
|
||||
await rootBundle.load('assets/test/images/sample.jpeg');
|
||||
final secondImage =
|
||||
await rootBundle.load('assets/test/images/sample.gif');
|
||||
final tempDirectory = await getTemporaryDirectory();
|
||||
|
||||
final firstImagePath = p.join(tempDirectory.path, 'sample.jpeg');
|
||||
final firstFile = File(firstImagePath)
|
||||
..writeAsBytesSync(firstImage.buffer.asUint8List());
|
||||
|
||||
final secondImagePath = p.join(tempDirectory.path, 'sample.gif');
|
||||
final secondFile = File(secondImagePath)
|
||||
..writeAsBytesSync(secondImage.buffer.asUint8List());
|
||||
|
||||
mockPickFilePaths(paths: [firstImagePath]);
|
||||
await getIt<KeyValueStorage>().set(KVKeys.kCloudType, '0');
|
||||
|
||||
// Click on add file button in the Media Cell Editor
|
||||
await tester.tap(find.text(LocaleKeys.grid_media_addFileOrImage.tr()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Tap on the upload interaction
|
||||
await tester.tapButtonWithName(
|
||||
LocaleKeys.document_plugins_file_fileUploadHint.tr(),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Expect one file
|
||||
expect(find.byType(RenderMedia), findsOneWidget);
|
||||
|
||||
// Mock second file
|
||||
mockPickFilePaths(paths: [secondImagePath]);
|
||||
|
||||
// Click on add file button in the Media Cell Editor
|
||||
await tester.tap(find.text(LocaleKeys.grid_media_addFileOrImage.tr()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Tap on the upload interaction
|
||||
await tester.tapButtonWithName(
|
||||
LocaleKeys.document_plugins_file_fileUploadHint.tr(),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Expect two files
|
||||
expect(find.byType(RenderMedia), findsNWidgets(2));
|
||||
|
||||
// Remove the temp files
|
||||
await Future.wait([firstFile.delete(), secondFile.delete()]);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/header/desktop_field_cell.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/row_detail.dart';
|
||||
import 'package:appflowy/util/field_type_extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/plugins/database/widgets/row/row_banner.dart';
|
||||
@@ -121,15 +124,24 @@ void main() {
|
||||
FieldType.Checkbox,
|
||||
]) {
|
||||
await tester.tapRowDetailPageCreatePropertyButton();
|
||||
await tester.renameField(fieldType.name);
|
||||
|
||||
// Open the type option menu
|
||||
await tester.tapSwitchFieldTypeButton();
|
||||
|
||||
await tester.selectFieldType(fieldType);
|
||||
|
||||
final field = find.descendant(
|
||||
of: find.byType(RowDetailPage),
|
||||
matching: find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is FieldCellButton &&
|
||||
widget.field.name == fieldType.i18n,
|
||||
),
|
||||
);
|
||||
expect(field, findsOneWidget);
|
||||
|
||||
// After update the field type, the cells should be updated
|
||||
await tester.findCellByFieldType(fieldType);
|
||||
tester.findCellByFieldType(fieldType);
|
||||
await tester.scrollRowDetailByOffset(const Offset(0, -50));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
@@ -25,6 +25,7 @@ void main() {
|
||||
const lines = 3;
|
||||
final text = List.generate(lines, (index) => 'line $index').join('\n');
|
||||
AppFlowyClipboard.mockSetData(AppFlowyClipboardData(text: text));
|
||||
ClipboardService.mockSetData(ClipboardServiceData(plainText: text));
|
||||
|
||||
await insertCodeBlockInDocument(tester);
|
||||
|
||||
@@ -51,7 +52,9 @@ Future<void> insertCodeBlockInDocument(WidgetTester tester) async {
|
||||
// open the actions menu and insert the codeBlock
|
||||
await tester.editor.showSlashMenu();
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
LocaleKeys.document_selectionMenu_codeBlock.tr(),
|
||||
LocaleKeys.document_slashMenu_name_code.tr(),
|
||||
offset: 150,
|
||||
);
|
||||
// wait for the codeBlock to be inserted
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
@@ -116,6 +116,91 @@ void main() {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('paste bulleted list in numbered list', (tester) async {
|
||||
const inAppJson =
|
||||
'{"document":{"type":"page","children":[{"type":"bulleted_list","children":[{"type":"bulleted_list","data":{"delta":[{"insert":"World"}]}}],"data":{"delta":[{"insert":"Hello"}]}}]}}';
|
||||
|
||||
await tester.pasteContent(
|
||||
inAppJson: inAppJson,
|
||||
beforeTest: (editorState) async {
|
||||
final transaction = editorState.transaction;
|
||||
// Insert two numbered list nodes
|
||||
// 1. Parent One
|
||||
// 2.
|
||||
transaction.insertNodes(
|
||||
[0],
|
||||
[
|
||||
Node(
|
||||
type: NumberedListBlockKeys.type,
|
||||
attributes: {
|
||||
'delta': [
|
||||
{"insert": "One"},
|
||||
],
|
||||
},
|
||||
),
|
||||
Node(
|
||||
type: NumberedListBlockKeys.type,
|
||||
attributes: {'delta': []},
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
// Set the selection to the second numbered list node (which has empty delta)
|
||||
transaction.afterSelection = Selection.collapsed(Position(path: [1]));
|
||||
|
||||
await editorState.apply(transaction);
|
||||
await tester.pumpAndSettle();
|
||||
},
|
||||
(editorState) {
|
||||
final secondNode = editorState.getNodeAtPath([1]);
|
||||
expect(secondNode?.delta?.toPlainText(), 'Hello');
|
||||
expect(secondNode?.children.length, 1);
|
||||
|
||||
final childNode = secondNode?.children.first;
|
||||
expect(childNode?.delta?.toPlainText(), 'World');
|
||||
expect(childNode?.type, BulletedListBlockKeys.type);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('paste text on part of bullet list', (tester) async {
|
||||
const plainText = 'test';
|
||||
|
||||
await tester.pasteContent(
|
||||
plainText: plainText,
|
||||
beforeTest: (editorState) async {
|
||||
final transaction = editorState.transaction;
|
||||
transaction.insertNodes(
|
||||
[0],
|
||||
[
|
||||
Node(
|
||||
type: BulletedListBlockKeys.type,
|
||||
attributes: {
|
||||
'delta': [
|
||||
{"insert": "bullet list"},
|
||||
],
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
// Set the selection to the second numbered list node (which has empty delta)
|
||||
transaction.afterSelection = Selection(
|
||||
start: Position(path: [0], offset: 7),
|
||||
end: Position(path: [0], offset: 11),
|
||||
);
|
||||
|
||||
await editorState.apply(transaction);
|
||||
await tester.pumpAndSettle();
|
||||
},
|
||||
(editorState) {
|
||||
final node = editorState.getNodeAtPath([0]);
|
||||
expect(node?.delta?.toPlainText(), 'bullet test');
|
||||
expect(node?.type, BulletedListBlockKeys.type);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('paste image(png) from memory', (tester) async {
|
||||
@@ -198,10 +283,6 @@ void main() {
|
||||
expect(editorState.document.root.children.length, 2);
|
||||
final node = editorState.getNodeAtPath([0])!;
|
||||
expect(node.type, ImageBlockKeys.type);
|
||||
expect(
|
||||
node.attributes[ImageBlockKeys.url],
|
||||
'https://user-images.githubusercontent.com/9403740/262918875-603f4adb-58dd-49b5-8201-341d354935fd.png',
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
@@ -210,42 +291,33 @@ void main() {
|
||||
testWidgets('paste the html content contains section', (tester) async {
|
||||
const html =
|
||||
'''<meta charset='utf-8'><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, "system-ui", "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><span style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgb(0, 160, 113);"><strong style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important;">AppFlowy</strong></span></section><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, "system-ui", "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><span style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgb(0, 160, 113);"><strong style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important;">Hello World</strong></span></section>''';
|
||||
await tester.pasteContent(
|
||||
html: html,
|
||||
(editorState) {
|
||||
expect(editorState.document.root.children.length, 2);
|
||||
final node1 = editorState.getNodeAtPath([0])!;
|
||||
final node2 = editorState.getNodeAtPath([1])!;
|
||||
expect(node1.type, ParagraphBlockKeys.type);
|
||||
expect(node2.type, ParagraphBlockKeys.type);
|
||||
},
|
||||
);
|
||||
await tester.pasteContent(html: html, (editorState) {
|
||||
expect(editorState.document.root.children.length, 2);
|
||||
final node1 = editorState.getNodeAtPath([0])!;
|
||||
final node2 = editorState.getNodeAtPath([1])!;
|
||||
expect(node1.type, ParagraphBlockKeys.type);
|
||||
expect(node2.type, ParagraphBlockKeys.type);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('paste the html from google translation', (tester) async {
|
||||
const html =
|
||||
'''<meta charset='utf-8'><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, "system-ui", "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><span style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgb(0, 160, 113);"><strong style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; vertical-align: inherit;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; vertical-align: inherit;">new force</font></font></strong></span></section><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, "system-ui", "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><span style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgb(0, 160, 113);"><strong style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; vertical-align: inherit;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; vertical-align: inherit;">Assessment focus: potential motivations, empathy</font></font></strong></span></section><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, "system-ui", "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><br style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important;"></section><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, "system-ui", "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-Line truncated
|
||||
await tester.pasteContent(
|
||||
html: html,
|
||||
(editorState) {
|
||||
expect(editorState.document.root.children.length, 8);
|
||||
},
|
||||
);
|
||||
await tester.pasteContent(html: html, (editorState) {
|
||||
expect(editorState.document.root.children.length, 8);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'auto convert url to link preview block',
|
||||
(widgetTester) async {
|
||||
(tester) async {
|
||||
const url = 'https://appflowy.io';
|
||||
await widgetTester.pasteContent(
|
||||
plainText: url,
|
||||
(editorState) {
|
||||
expect(editorState.document.root.children.length, 2);
|
||||
final node = editorState.getNodeAtPath([0])!;
|
||||
expect(node.type, LinkPreviewBlockKeys.type);
|
||||
expect(node.attributes[LinkPreviewBlockKeys.url], url);
|
||||
},
|
||||
);
|
||||
await tester.pasteContent(plainText: url, (editorState) {
|
||||
expect(editorState.document.root.children.length, 2);
|
||||
final node = editorState.getNodeAtPath([0])!;
|
||||
expect(node.type, LinkPreviewBlockKeys.type);
|
||||
expect(node.attributes[LinkPreviewBlockKeys.url], url);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -256,6 +328,7 @@ extension on WidgetTester {
|
||||
Future<void> Function(EditorState editorState)? beforeTest,
|
||||
String? plainText,
|
||||
String? html,
|
||||
String? inAppJson,
|
||||
(String, Uint8List?)? image,
|
||||
}) async {
|
||||
await initializeAppFlowy();
|
||||
@@ -271,6 +344,7 @@ extension on WidgetTester {
|
||||
ClipboardServiceData(
|
||||
plainText: plainText,
|
||||
html: html,
|
||||
inAppJson: inAppJson,
|
||||
image: image,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -12,10 +12,13 @@ import 'document_more_actions_test.dart' as document_more_actions_test;
|
||||
import 'document_text_direction_test.dart' as document_text_direction_test;
|
||||
import 'document_with_cover_image_test.dart' as document_with_cover_image_test;
|
||||
import 'document_with_database_test.dart' as document_with_database_test;
|
||||
import 'document_with_file_test.dart' as document_with_file_test;
|
||||
import 'document_with_image_block_test.dart' as document_with_image_block_test;
|
||||
import 'document_with_inline_math_equation_test.dart'
|
||||
as document_with_inline_math_equation_test;
|
||||
import 'document_with_inline_page_test.dart' as document_with_inline_page_test;
|
||||
import 'document_with_multi_image_block_test.dart'
|
||||
as document_with_multi_image_block_test;
|
||||
import 'document_with_outline_block_test.dart' as document_with_outline_block;
|
||||
import 'document_with_toggle_list_test.dart' as document_with_toggle_list_test;
|
||||
import 'edit_document_test.dart' as document_edit_test;
|
||||
@@ -38,6 +41,8 @@ void startTesting() {
|
||||
document_text_direction_test.main();
|
||||
document_option_action_test.main();
|
||||
document_with_image_block_test.main();
|
||||
document_with_multi_image_block_test.main();
|
||||
document_inline_page_reference_test.main();
|
||||
document_more_actions_test.main();
|
||||
document_with_file_test.main();
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.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';
|
||||
@@ -7,7 +6,6 @@ import 'package:appflowy/plugins/inline_actions/widgets/inline_actions_handler.d
|
||||
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';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
@@ -22,7 +20,7 @@ void main() {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await insertReferenceDatabase(tester, ViewLayoutPB.Grid);
|
||||
await insertLinkedDatabase(tester, ViewLayoutPB.Grid);
|
||||
|
||||
// validate the referenced grid is inserted
|
||||
expect(
|
||||
@@ -50,7 +48,7 @@ void main() {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await insertReferenceDatabase(tester, ViewLayoutPB.Board);
|
||||
await insertLinkedDatabase(tester, ViewLayoutPB.Board);
|
||||
|
||||
// validate the referenced board is inserted
|
||||
expect(
|
||||
@@ -66,7 +64,7 @@ void main() {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await insertReferenceDatabase(tester, ViewLayoutPB.Calendar);
|
||||
await insertLinkedDatabase(tester, ViewLayoutPB.Calendar);
|
||||
|
||||
// validate the referenced grid is inserted
|
||||
expect(
|
||||
@@ -129,7 +127,7 @@ void main() {
|
||||
}
|
||||
|
||||
/// Insert a referenced database of [layout] into the document
|
||||
Future<void> insertReferenceDatabase(
|
||||
Future<void> insertLinkedDatabase(
|
||||
WidgetTester tester,
|
||||
ViewLayoutPB layout,
|
||||
) async {
|
||||
@@ -150,7 +148,7 @@ Future<void> insertReferenceDatabase(
|
||||
// insert a referenced view
|
||||
await tester.editor.showSlashMenu();
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
layout.referencedMenuName,
|
||||
layout.slashMenuLinkedName,
|
||||
);
|
||||
|
||||
final linkToPageMenu = find.byType(InlineActionsHandler);
|
||||
@@ -176,16 +174,9 @@ Future<void> createInlineDatabase(
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
// insert a referenced view
|
||||
await tester.editor.showSlashMenu();
|
||||
final name = switch (layout) {
|
||||
ViewLayoutPB.Grid => LocaleKeys.document_slashMenu_grid_createANewGrid.tr(),
|
||||
ViewLayoutPB.Board =>
|
||||
LocaleKeys.document_slashMenu_board_createANewBoard.tr(),
|
||||
ViewLayoutPB.Calendar =>
|
||||
LocaleKeys.document_slashMenu_calendar_createANewCalendar.tr(),
|
||||
_ => '',
|
||||
};
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
name,
|
||||
layout.slashMenuName,
|
||||
offset: 100,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
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/file/file_upload_menu.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import '../../shared/mock/mock_file_picker.dart';
|
||||
import '../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('file block in document', () {
|
||||
testWidgets('insert a file from local file + rename file', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(name: 'Insert file test');
|
||||
|
||||
// tap the first line of the document
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.editor.showSlashMenu();
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
LocaleKeys.document_slashMenu_name_file.tr(),
|
||||
);
|
||||
expect(find.byType(FileBlockComponent), findsOneWidget);
|
||||
|
||||
await tester.tap(find.byType(FileBlockComponent));
|
||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
|
||||
expect(find.byType(FileUploadMenu), findsOneWidget);
|
||||
|
||||
final image = await rootBundle.load('assets/test/images/sample.jpeg');
|
||||
final tempDirectory = await getTemporaryDirectory();
|
||||
final filePath = p.join(tempDirectory.path, 'sample.jpeg');
|
||||
final file = File(filePath)..writeAsBytesSync(image.buffer.asUint8List());
|
||||
|
||||
mockPickFilePaths(paths: [filePath]);
|
||||
|
||||
await getIt<KeyValueStorage>().set(KVKeys.kCloudType, '0');
|
||||
await tester.tap(
|
||||
find.text(LocaleKeys.document_plugins_file_fileUploadHint.tr()),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(FileUploadMenu), findsNothing);
|
||||
expect(find.byType(FileBlockComponent), findsOneWidget);
|
||||
|
||||
final node = tester.editor.getCurrentEditorState().getNodeAtPath([0])!;
|
||||
expect(node.type, FileBlockKeys.type);
|
||||
expect(node.attributes[FileBlockKeys.url], isNotEmpty);
|
||||
expect(
|
||||
node.attributes[FileBlockKeys.urlType],
|
||||
FileUrlType.local.toIntValue(),
|
||||
);
|
||||
|
||||
// Check the name of the file is correctly extracted
|
||||
expect(node.attributes[FileBlockKeys.name], 'sample.jpeg');
|
||||
expect(find.text('sample.jpeg'), findsOneWidget);
|
||||
|
||||
const newName = "Renamed file";
|
||||
|
||||
// Hover on the widget to see the three dots to open FileBlockMenu
|
||||
await tester.hoverOnWidget(
|
||||
find.byType(FileBlockComponent),
|
||||
onHover: () async {
|
||||
await tester.tap(find.byType(FileMenuTrigger));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(
|
||||
find.text(LocaleKeys.document_plugins_file_renameFile_title.tr()),
|
||||
);
|
||||
},
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(FlowyTextField), findsOneWidget);
|
||||
await tester.enterText(find.byType(FlowyTextField), newName);
|
||||
await tester.pump();
|
||||
|
||||
await tester.tap(find.text(LocaleKeys.button_save.tr()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final updatedNode =
|
||||
tester.editor.getCurrentEditorState().getNodeAtPath([0])!;
|
||||
expect(updatedNode.attributes[FileBlockKeys.name], newName);
|
||||
expect(find.text(newName), findsOneWidget);
|
||||
|
||||
// remove the temp file
|
||||
file.deleteSync();
|
||||
});
|
||||
|
||||
testWidgets('insert a file from network', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(name: 'Insert file test');
|
||||
|
||||
// tap the first line of the document
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.editor.showSlashMenu();
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
LocaleKeys.document_slashMenu_name_file.tr(),
|
||||
);
|
||||
expect(find.byType(FileBlockComponent), findsOneWidget);
|
||||
|
||||
await tester.tap(find.byType(FileBlockComponent));
|
||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
expect(find.byType(FileUploadMenu), findsOneWidget);
|
||||
|
||||
// Navigate to integrate link tab
|
||||
await tester.tapButtonWithName(
|
||||
LocaleKeys.document_plugins_file_networkTab.tr(),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
const url =
|
||||
'https://images.unsplash.com/photo-1469474968028-56623f02e42e?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&dl=david-marcu-78A265wPiO4-unsplash.jpg&w=640';
|
||||
await tester.enterText(
|
||||
find.descendant(
|
||||
of: find.byType(FileUploadMenu),
|
||||
matching: find.byType(FlowyTextField),
|
||||
),
|
||||
url,
|
||||
);
|
||||
await tester.tapButton(
|
||||
find.descendant(
|
||||
of: find.byType(FileUploadMenu),
|
||||
matching: find.text(
|
||||
LocaleKeys.document_plugins_file_networkAction.tr(),
|
||||
findRichText: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(FileUploadMenu), findsNothing);
|
||||
expect(find.byType(FileBlockComponent), findsOneWidget);
|
||||
|
||||
final node = tester.editor.getCurrentEditorState().getNodeAtPath([0])!;
|
||||
expect(node.type, FileBlockKeys.type);
|
||||
expect(node.attributes[FileBlockKeys.url], isNotEmpty);
|
||||
expect(
|
||||
node.attributes[FileBlockKeys.urlType],
|
||||
FileUrlType.network.toIntValue(),
|
||||
);
|
||||
|
||||
// Check the name is correctly extracted from the url
|
||||
expect(
|
||||
node.attributes[FileBlockKeys.name],
|
||||
'photo-1469474968028-56623f02e42e',
|
||||
);
|
||||
expect(find.text('photo-1469474968028-56623f02e42e'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -3,12 +3,12 @@ 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/custom_image_block_component/custom_image_block_component.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/plugins/document/presentation/editor_plugins/image/upload_image_menu/upload_image_menu.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/embed_image_url_widget.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart'
|
||||
hide UploadImageMenu, ResizableImage;
|
||||
@@ -36,13 +36,15 @@ void main() {
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: LocaleKeys.document_plugins_image_addAnImage.tr(),
|
||||
name: LocaleKeys.document_plugins_image_addAnImageDesktop.tr(),
|
||||
);
|
||||
|
||||
// tap the first line of the document
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.editor.showSlashMenu();
|
||||
await tester.editor.tapSlashMenuItemWithName('Image');
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
LocaleKeys.document_slashMenu_name_image.tr(),
|
||||
);
|
||||
expect(find.byType(CustomImageBlockComponent), findsOneWidget);
|
||||
expect(find.byType(ImagePlaceholder), findsOneWidget);
|
||||
expect(
|
||||
@@ -84,13 +86,15 @@ void main() {
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: LocaleKeys.document_plugins_image_addAnImage.tr(),
|
||||
name: LocaleKeys.document_plugins_image_addAnImageDesktop.tr(),
|
||||
);
|
||||
|
||||
// tap the first line of the document
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.editor.showSlashMenu();
|
||||
await tester.editor.tapSlashMenuItemWithName('Image');
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
LocaleKeys.document_slashMenu_name_image.tr(),
|
||||
);
|
||||
expect(find.byType(CustomImageBlockComponent), findsOneWidget);
|
||||
expect(find.byType(ImagePlaceholder), findsOneWidget);
|
||||
expect(
|
||||
@@ -137,13 +141,15 @@ void main() {
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: LocaleKeys.document_plugins_image_addAnImage.tr(),
|
||||
name: LocaleKeys.document_plugins_image_addAnImageDesktop.tr(),
|
||||
);
|
||||
|
||||
// tap the first line of the document
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.editor.showSlashMenu();
|
||||
await tester.editor.tapSlashMenuItemWithName('Image');
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
LocaleKeys.document_slashMenu_name_image.tr(),
|
||||
);
|
||||
expect(find.byType(CustomImageBlockComponent), findsOneWidget);
|
||||
expect(find.byType(ImagePlaceholder), findsOneWidget);
|
||||
expect(
|
||||
@@ -161,5 +167,69 @@ void main() {
|
||||
expect(find.byType(UnsplashImageWidget), findsOneWidget);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('insert two images from local file at once', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: LocaleKeys.document_plugins_image_addAnImageDesktop.tr(),
|
||||
);
|
||||
|
||||
// tap the first line of the document
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.editor.showSlashMenu();
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
LocaleKeys.document_slashMenu_name_image.tr(),
|
||||
);
|
||||
expect(find.byType(CustomImageBlockComponent), findsOneWidget);
|
||||
expect(find.byType(ImagePlaceholder), findsOneWidget);
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(ImagePlaceholder),
|
||||
matching: find.byType(AppFlowyPopover),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.byType(UploadImageMenu), findsOneWidget);
|
||||
|
||||
final firstImage =
|
||||
await rootBundle.load('assets/test/images/sample.jpeg');
|
||||
final secondImage =
|
||||
await rootBundle.load('assets/test/images/sample.gif');
|
||||
final tempDirectory = await getTemporaryDirectory();
|
||||
|
||||
final firstImagePath = p.join(tempDirectory.path, 'sample.jpeg');
|
||||
final firstFile = File(firstImagePath)
|
||||
..writeAsBytesSync(firstImage.buffer.asUint8List());
|
||||
|
||||
final secondImagePath = p.join(tempDirectory.path, 'sample.gif');
|
||||
final secondFile = File(secondImagePath)
|
||||
..writeAsBytesSync(secondImage.buffer.asUint8List());
|
||||
|
||||
mockPickFilePaths(paths: [firstImagePath, secondImagePath]);
|
||||
|
||||
await getIt<KeyValueStorage>().set(KVKeys.kCloudType, '0');
|
||||
await tester.tapButtonWithName(
|
||||
LocaleKeys.document_imageBlock_upload_placeholder.tr(),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(ResizableImage), findsNWidgets(2));
|
||||
|
||||
final firstNode =
|
||||
tester.editor.getCurrentEditorState().getNodeAtPath([0])!;
|
||||
expect(firstNode.type, ImageBlockKeys.type);
|
||||
expect(firstNode.attributes[ImageBlockKeys.url], isNotEmpty);
|
||||
|
||||
final secondNode =
|
||||
tester.editor.getCurrentEditorState().getNodeAtPath([0])!;
|
||||
expect(secondNode.type, ImageBlockKeys.type);
|
||||
expect(secondNode.attributes[ImageBlockKeys.url], isNotEmpty);
|
||||
|
||||
// remove the temp files
|
||||
await Future.wait([firstFile.delete(), secondFile.delete()]);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -33,7 +33,7 @@ void main() {
|
||||
);
|
||||
|
||||
// tap the inline math equation button
|
||||
final inlineMathEquationButton = find.byTooltip(
|
||||
final inlineMathEquationButton = find.findFlowyTooltip(
|
||||
LocaleKeys.document_plugins_createInlineMathEquation.tr(),
|
||||
);
|
||||
await tester.tapButton(inlineMathEquationButton);
|
||||
@@ -78,7 +78,7 @@ void main() {
|
||||
);
|
||||
|
||||
// tap the inline math equation button
|
||||
var inlineMathEquationButton = find.byTooltip(
|
||||
var inlineMathEquationButton = find.findFlowyTooltip(
|
||||
LocaleKeys.document_plugins_createInlineMathEquation.tr(),
|
||||
);
|
||||
await tester.tapButton(inlineMathEquationButton);
|
||||
@@ -93,11 +93,11 @@ void main() {
|
||||
);
|
||||
|
||||
// expect to the see the inline math equation button is highlighted
|
||||
inlineMathEquationButton = find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is SVGIconItemWidget &&
|
||||
widget.tooltip ==
|
||||
LocaleKeys.document_plugins_createInlineMathEquation.tr(),
|
||||
inlineMathEquationButton = find.descendant(
|
||||
of: find.findFlowyTooltip(
|
||||
LocaleKeys.document_plugins_createInlineMathEquation.tr(),
|
||||
),
|
||||
matching: find.byType(SVGIconItemWidget),
|
||||
);
|
||||
expect(
|
||||
tester.widget<SVGIconItemWidget>(inlineMathEquationButton).isHighlight,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart';
|
||||
import 'package:appflowy/shared/flowy_error_page.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flowy_infra_ui/widget/error_page.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
@@ -92,7 +92,7 @@ void main() {
|
||||
);
|
||||
expect(finder, findsOneWidget);
|
||||
await tester.tapButton(finder);
|
||||
expect(find.byType(FlowyErrorPage), findsOneWidget);
|
||||
expect(find.byType(AppFlowyErrorPage), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/core/config/kv.dart';
|
||||
import 'package:appflowy/core/config/kv_keys.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/image_render.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/layouts/image_browser_layout.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_placeholder.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/upload_image_menu/upload_image_menu.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/embed_image_url_widget.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy/shared/appflowy_network_image.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/image_viewer/interactive_image_toolbar.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/image_viewer/interactive_image_viewer.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import '../../shared/mock/mock_file_picker.dart';
|
||||
import '../../shared/util.dart';
|
||||
import '../board/board_hide_groups_test.dart';
|
||||
|
||||
void main() {
|
||||
setUp(() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
});
|
||||
|
||||
group('multi image block in document', () {
|
||||
testWidgets('insert images from local and use interactive viewer',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'multi image block test',
|
||||
);
|
||||
|
||||
// tap the first line of the document
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.editor.showSlashMenu();
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
LocaleKeys.document_slashMenu_name_photoGallery.tr(),
|
||||
offset: 100,
|
||||
);
|
||||
expect(find.byType(MultiImageBlockComponent), findsOneWidget);
|
||||
expect(find.byType(MultiImagePlaceholder), findsOneWidget);
|
||||
|
||||
await tester.tap(find.byType(MultiImagePlaceholder));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(UploadImageMenu), findsOneWidget);
|
||||
|
||||
final firstImage =
|
||||
await rootBundle.load('assets/test/images/sample.jpeg');
|
||||
final secondImage =
|
||||
await rootBundle.load('assets/test/images/sample.gif');
|
||||
final tempDirectory = await getTemporaryDirectory();
|
||||
|
||||
final firstImagePath = p.join(tempDirectory.path, 'sample.jpeg');
|
||||
final firstFile = File(firstImagePath)
|
||||
..writeAsBytesSync(firstImage.buffer.asUint8List());
|
||||
|
||||
final secondImagePath = p.join(tempDirectory.path, 'sample.gif');
|
||||
final secondFile = File(secondImagePath)
|
||||
..writeAsBytesSync(secondImage.buffer.asUint8List());
|
||||
|
||||
mockPickFilePaths(paths: [firstImagePath, secondImagePath]);
|
||||
|
||||
await getIt<KeyValueStorage>().set(KVKeys.kCloudType, '0');
|
||||
await tester.tapButtonWithName(
|
||||
LocaleKeys.document_imageBlock_upload_placeholder.tr(),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(ImageBrowserLayout), findsOneWidget);
|
||||
final node = tester.editor.getCurrentEditorState().getNodeAtPath([0])!;
|
||||
expect(node.type, MultiImageBlockKeys.type);
|
||||
|
||||
final data = MultiImageData.fromJson(
|
||||
node.attributes[MultiImageBlockKeys.images],
|
||||
);
|
||||
|
||||
expect(data.images.length, 2);
|
||||
|
||||
// Start using the interactive viewer to view the image(s)
|
||||
final imageFinder = find
|
||||
.byWidgetPredicate(
|
||||
(w) =>
|
||||
w is Image &&
|
||||
w.image is FileImage &&
|
||||
(w.image as FileImage).file.path.endsWith('.jpeg'),
|
||||
)
|
||||
.first;
|
||||
await tester.tap(imageFinder);
|
||||
await tester.pump(kDoubleTapMinTime);
|
||||
await tester.tap(imageFinder);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final ivFinder = find.byType(InteractiveImageViewer);
|
||||
expect(ivFinder, findsOneWidget);
|
||||
|
||||
// go to next image
|
||||
await tester.tap(find.byFlowySvg(FlowySvgs.arrow_right_s));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Expect image to end with .gif
|
||||
final gifImageFinder = find.byWidgetPredicate(
|
||||
(w) =>
|
||||
w is Image &&
|
||||
w.image is FileImage &&
|
||||
(w.image as FileImage).file.path.endsWith('.gif'),
|
||||
);
|
||||
|
||||
gifImageFinder.evaluate();
|
||||
expect(gifImageFinder.found.length, 2);
|
||||
|
||||
// go to previous image
|
||||
await tester.tap(find.byFlowySvg(FlowySvgs.arrow_left_s));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
gifImageFinder.evaluate();
|
||||
expect(gifImageFinder.found.length, 1);
|
||||
|
||||
// remove the temp files
|
||||
await Future.wait([firstFile.delete(), secondFile.delete()]);
|
||||
});
|
||||
|
||||
testWidgets('insert and delete images from network', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'multi image block test',
|
||||
);
|
||||
|
||||
// tap the first line of the document
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.editor.showSlashMenu();
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
LocaleKeys.document_slashMenu_name_photoGallery.tr(),
|
||||
offset: 100,
|
||||
);
|
||||
expect(find.byType(MultiImageBlockComponent), findsOneWidget);
|
||||
expect(find.byType(MultiImagePlaceholder), findsOneWidget);
|
||||
|
||||
await tester.tap(find.byType(MultiImagePlaceholder));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(UploadImageMenu), findsOneWidget);
|
||||
|
||||
await tester.tapButtonWithName(
|
||||
LocaleKeys.document_imageBlock_embedLink_label.tr(),
|
||||
);
|
||||
|
||||
const url =
|
||||
'https://images.unsplash.com/photo-1469474968028-56623f02e42e?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&dl=david-marcu-78A265wPiO4-unsplash.jpg&w=640';
|
||||
await tester.enterText(
|
||||
find.descendant(
|
||||
of: find.byType(EmbedImageUrlWidget),
|
||||
matching: find.byType(TextField),
|
||||
),
|
||||
url,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tapButton(
|
||||
find.descendant(
|
||||
of: find.byType(EmbedImageUrlWidget),
|
||||
matching: find.text(
|
||||
LocaleKeys.document_imageBlock_embedLink_label.tr(),
|
||||
findRichText: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(ImageBrowserLayout), findsOneWidget);
|
||||
final node = tester.editor.getCurrentEditorState().getNodeAtPath([0])!;
|
||||
expect(node.type, MultiImageBlockKeys.type);
|
||||
|
||||
final data = MultiImageData.fromJson(
|
||||
node.attributes[MultiImageBlockKeys.images],
|
||||
);
|
||||
|
||||
expect(data.images.length, 1);
|
||||
|
||||
final imageFinder = find
|
||||
.byWidgetPredicate(
|
||||
(w) => w is FlowyNetworkImage && w.url == url,
|
||||
)
|
||||
.first;
|
||||
|
||||
// Insert two images from network
|
||||
for (int i = 0; i < 2; i++) {
|
||||
// Hover on the image to show the image toolbar
|
||||
await tester.hoverOnWidget(
|
||||
imageFinder,
|
||||
onHover: () async {
|
||||
// Click on the add
|
||||
final addFinder = find.descendant(
|
||||
of: find.byType(MultiImageMenu),
|
||||
matching: find.byFlowySvg(FlowySvgs.add_s),
|
||||
);
|
||||
|
||||
expect(addFinder, findsOneWidget);
|
||||
await tester.tap(addFinder);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tapButtonWithName(
|
||||
LocaleKeys.document_imageBlock_embedLink_label.tr(),
|
||||
);
|
||||
|
||||
await tester.enterText(
|
||||
find.descendant(
|
||||
of: find.byType(EmbedImageUrlWidget),
|
||||
matching: find.byType(TextField),
|
||||
),
|
||||
url,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tapButton(
|
||||
find.descendant(
|
||||
of: find.byType(EmbedImageUrlWidget),
|
||||
matching: find.text(
|
||||
LocaleKeys.document_imageBlock_embedLink_label.tr(),
|
||||
findRichText: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// There should be 4 images visible now, where 2 are thumbnails
|
||||
expect(find.byType(ThumbnailItem), findsNWidgets(3));
|
||||
|
||||
// And all three use ImageRender
|
||||
expect(find.byType(ImageRender), findsNWidgets(4));
|
||||
|
||||
// Hover on and delete the first thumbnail image
|
||||
await tester.hoverOnWidget(find.byType(ThumbnailItem).first);
|
||||
|
||||
final deleteFinder = find
|
||||
.descendant(
|
||||
of: find.byType(ThumbnailItem),
|
||||
matching: find.byFlowySvg(FlowySvgs.delete_s),
|
||||
)
|
||||
.first;
|
||||
|
||||
expect(deleteFinder, findsOneWidget);
|
||||
await tester.tap(deleteFinder);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(ImageRender), findsNWidgets(3));
|
||||
|
||||
// Delete one from interactive viewer
|
||||
await tester.tap(imageFinder);
|
||||
await tester.pump(kDoubleTapMinTime);
|
||||
await tester.tap(imageFinder);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final ivFinder = find.byType(InteractiveImageViewer);
|
||||
expect(ivFinder, findsOneWidget);
|
||||
|
||||
await tester.tap(
|
||||
find.descendant(
|
||||
of: find.byType(InteractiveImageToolbar),
|
||||
matching: find.byFlowySvg(FlowySvgs.delete_s),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(InteractiveImageViewer), findsNothing);
|
||||
|
||||
// There should be 1 image and the thumbnail for said image still visible
|
||||
expect(find.byType(ImageRender), findsNWidgets(2));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -171,7 +171,8 @@ Future<void> insertOutlineInDocument(WidgetTester tester) async {
|
||||
// open the actions menu and insert the outline block
|
||||
await tester.editor.showSlashMenu();
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
LocaleKeys.document_selectionMenu_outline.tr(),
|
||||
LocaleKeys.document_slashMenu_name_outline.tr(),
|
||||
offset: 100,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
@@ -8,9 +8,10 @@ void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('Empty', () {
|
||||
testWidgets('toggle theme mode', (tester) async {
|
||||
testWidgets('empty test', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.wait(1000);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -82,11 +82,11 @@ void main() {
|
||||
HeadingBlockKeys.type,
|
||||
);
|
||||
expect(
|
||||
importedPageEditorState.getNodeAtPath([2])!.type,
|
||||
importedPageEditorState.getNodeAtPath([1])!.type,
|
||||
HeadingBlockKeys.type,
|
||||
);
|
||||
expect(
|
||||
importedPageEditorState.getNodeAtPath([4])!.type,
|
||||
importedPageEditorState.getNodeAtPath([2])!.type,
|
||||
TableBlockKeys.type,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/openai_client.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/render/toolbar/toolbar_widget.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../../shared/mock/mock_openai_repository.dart';
|
||||
import '../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
const service = TestWorkspaceService(TestWorkspace.aiWorkSpace);
|
||||
|
||||
group('integration tests for open-ai smart menu', () {
|
||||
setUpAll(() async => service.setUpAll());
|
||||
setUp(() async => service.setUp());
|
||||
|
||||
testWidgets('testing selection on open-ai smart menu replace',
|
||||
(tester) async {
|
||||
final appFlowyEditor = await setUpOpenAITesting(tester);
|
||||
final editorState = appFlowyEditor.editorState;
|
||||
|
||||
editorState.service.selectionService.updateSelection(
|
||||
Selection(
|
||||
start: Position(path: [1], offset: 4),
|
||||
end: Position(path: [1], offset: 10),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(ToolbarWidget), findsAtLeastNWidgets(1));
|
||||
|
||||
await tester.tap(find.byTooltip('AI Assistants'));
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||
|
||||
await tester.tap(find.text('Summarize'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester
|
||||
.tap(find.byType(FlowyRichTextButton, skipOffstage: false).first);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
editorState.service.selectionService.currentSelection.value,
|
||||
Selection(
|
||||
start: Position(path: [1], offset: 4),
|
||||
end: Position(path: [1], offset: 84),
|
||||
),
|
||||
);
|
||||
});
|
||||
testWidgets('testing selection on open-ai smart menu insert',
|
||||
(tester) async {
|
||||
final appFlowyEditor = await setUpOpenAITesting(tester);
|
||||
final editorState = appFlowyEditor.editorState;
|
||||
|
||||
editorState.service.selectionService.updateSelection(
|
||||
Selection(
|
||||
start: Position(path: [1]),
|
||||
end: Position(path: [1], offset: 5),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(ToolbarWidget), findsAtLeastNWidgets(1));
|
||||
|
||||
await tester.tap(find.byTooltip('AI Assistants'));
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||
|
||||
await tester.tap(find.text('Summarize'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester
|
||||
.tap(find.byType(FlowyRichTextButton, skipOffstage: false).at(1));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
editorState.service.selectionService.currentSelection.value,
|
||||
Selection(
|
||||
start: Position(path: [2]),
|
||||
end: Position(path: [3]),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Future<AppFlowyEditor> setUpOpenAITesting(WidgetTester tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await mockOpenAIRepository();
|
||||
|
||||
await simulateKeyDownEvent(LogicalKeyboardKey.controlLeft);
|
||||
await simulateKeyDownEvent(LogicalKeyboardKey.backslash);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final Finder editor = find.byType(AppFlowyEditor);
|
||||
await tester.tap(editor);
|
||||
await tester.pumpAndSettle();
|
||||
return tester.state(editor).widget as AppFlowyEditor;
|
||||
}
|
||||
|
||||
Future<void> mockOpenAIRepository() async {
|
||||
await getIt.unregister<OpenAIRepository>();
|
||||
getIt.registerFactoryAsync<OpenAIRepository>(
|
||||
() => Future.value(
|
||||
MockOpenAIRepository(),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/plugins/document/presentation/share/share_button.dart';
|
||||
import 'package:appflowy/plugins/shared/share/share_button.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
@@ -51,13 +51,13 @@ void main() {
|
||||
},
|
||||
);
|
||||
|
||||
final shareButton = find.byType(ShareActionList);
|
||||
final shareButtonState =
|
||||
tester.state(shareButton) as ShareActionListState;
|
||||
final shareButton = find.byType(ShareButton);
|
||||
final shareButtonState = tester.widget(shareButton) as ShareButton;
|
||||
|
||||
final path = await mockSaveFilePath(
|
||||
p.join(
|
||||
context.applicationDataDirectory,
|
||||
'${shareButtonState.name}.md',
|
||||
'${shareButtonState.view.name}.md',
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/account/account.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_supabase_cloud.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'util.dart';
|
||||
|
||||
extension AppFlowyAuthTest on WidgetTester {
|
||||
Future<void> tapGoogleLoginInButton() async {
|
||||
await tapButton(find.byKey(const Key('signInWithGoogleButton')));
|
||||
await tapButton(
|
||||
find.byKey(signInWithGoogleButtonKey),
|
||||
);
|
||||
}
|
||||
|
||||
/// Requires being on the SettingsPage.account of the SettingsDialog
|
||||
Future<void> logout() async {
|
||||
final scrollable = find.findSettingsScrollable();
|
||||
await scrollUntilVisible(
|
||||
find.byType(SignInOutButton),
|
||||
find.byType(AccountSignInOutButton),
|
||||
100,
|
||||
scrollable: scrollable,
|
||||
);
|
||||
|
||||
await tapButton(find.byType(SignInOutButton));
|
||||
await tapButton(find.byType(AccountSignInOutButton));
|
||||
|
||||
expectToSeeText(LocaleKeys.button_confirm.tr());
|
||||
await tapButtonWithName(LocaleKeys.button_confirm.tr());
|
||||
expectToSeeText(LocaleKeys.button_ok.tr());
|
||||
await tapButtonWithName(LocaleKeys.button_ok.tr());
|
||||
}
|
||||
|
||||
Future<void> tapSignInAsGuest() async {
|
||||
@@ -36,7 +36,7 @@ extension AppFlowyAuthTest on WidgetTester {
|
||||
}
|
||||
|
||||
void expectToSeeGoogleLoginButton() {
|
||||
expect(find.byKey(const Key('signInWithGoogleButton')), findsOneWidget);
|
||||
expect(find.byKey(signInWithGoogleButtonKey), findsOneWidget);
|
||||
}
|
||||
|
||||
void assertSwitchValue(Finder finder, bool value) {
|
||||
@@ -51,26 +51,6 @@ extension AppFlowyAuthTest on WidgetTester {
|
||||
assert(isSwitched == value);
|
||||
}
|
||||
|
||||
void assertEnableEncryptSwitchValue(bool value) {
|
||||
assertSwitchValue(
|
||||
find.descendant(
|
||||
of: find.byType(EnableEncrypt),
|
||||
matching: find.byWidgetPredicate((widget) => widget is Switch),
|
||||
),
|
||||
value,
|
||||
);
|
||||
}
|
||||
|
||||
void assertSupabaseEnableSyncSwitchValue(bool value) {
|
||||
assertSwitchValue(
|
||||
find.descendant(
|
||||
of: find.byType(SupabaseEnableSync),
|
||||
matching: find.byWidgetPredicate((widget) => widget is Switch),
|
||||
),
|
||||
value,
|
||||
);
|
||||
}
|
||||
|
||||
void assertAppFlowyCloudEnableSyncSwitchValue(bool value) {
|
||||
assertToggleValue(
|
||||
find.descendant(
|
||||
@@ -81,15 +61,6 @@ extension AppFlowyAuthTest on WidgetTester {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> toggleEnableEncrypt() async {
|
||||
final finder = find.descendant(
|
||||
of: find.byType(EnableEncrypt),
|
||||
matching: find.byWidgetPredicate((widget) => widget is Switch),
|
||||
);
|
||||
|
||||
await tapButton(finder);
|
||||
}
|
||||
|
||||
Future<void> toggleEnableSync(Type syncButton) async {
|
||||
final finder = find.descendant(
|
||||
of: find.byType(syncButton),
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/env/cloud_env_test.dart';
|
||||
import 'package:appflowy/startup/entry_point.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
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:flowy_infra/uuid.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
@@ -56,8 +54,6 @@ extension AppFlowyTestBase on WidgetTester {
|
||||
switch (cloudType) {
|
||||
case AuthenticatorType.local:
|
||||
break;
|
||||
case AuthenticatorType.supabase:
|
||||
break;
|
||||
case AuthenticatorType.appflowyCloudSelfHost:
|
||||
rustEnvs["GOTRUE_ADMIN_EMAIL"] = "admin@example.com";
|
||||
rustEnvs["GOTRUE_ADMIN_PASSWORD"] = "password";
|
||||
@@ -76,13 +72,6 @@ extension AppFlowyTestBase on WidgetTester {
|
||||
case AuthenticatorType.local:
|
||||
await useLocalServer();
|
||||
break;
|
||||
case AuthenticatorType.supabase:
|
||||
await useTestSupabaseCloud();
|
||||
getIt.unregister<AuthService>();
|
||||
getIt.registerFactory<AuthService>(
|
||||
() => SupabaseMockAuthService(),
|
||||
);
|
||||
break;
|
||||
case AuthenticatorType.appflowyCloudSelfHost:
|
||||
await useTestSelfHostedAppFlowyCloud();
|
||||
getIt.unregister<AuthService>();
|
||||
@@ -231,13 +220,16 @@ extension AppFlowyFinderTestBase on CommonFinders {
|
||||
(widget) => widget is FlowyText && widget.text == text,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> useTestSupabaseCloud() async {
|
||||
await useSupabaseCloud(
|
||||
url: TestEnv.supabaseUrl,
|
||||
anonKey: TestEnv.supabaseAnonKey,
|
||||
);
|
||||
Finder findFlowyTooltip(String richMessage, {bool skipOffstage = true}) {
|
||||
return byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is FlowyTooltip &&
|
||||
widget.richMessage != null &&
|
||||
widget.richMessage!.toPlainText().contains(richMessage),
|
||||
skipOffstage: skipOffstage,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> useTestSelfHostedAppFlowyCloud() async {
|
||||
|
||||
@@ -11,7 +11,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/presentation.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/share/share_button.dart';
|
||||
import 'package:appflowy/plugins/shared/share/share_button.dart';
|
||||
import 'package:appflowy/shared/feature_flags.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/presentation/screens/screens.dart';
|
||||
@@ -259,7 +259,7 @@ extension CommonOperations on WidgetTester {
|
||||
/// Tap the share button above the document page.
|
||||
Future<void> tapShareButton() async {
|
||||
final shareButton = find.byWidgetPredicate(
|
||||
(widget) => widget is DocumentShareButton,
|
||||
(widget) => widget is ShareButton,
|
||||
);
|
||||
await tapButton(shareButton);
|
||||
}
|
||||
@@ -663,4 +663,34 @@ extension ViewLayoutPBTest on ViewLayoutPB {
|
||||
throw UnsupportedError('Unsupported layout: $this');
|
||||
}
|
||||
}
|
||||
|
||||
String get slashMenuName {
|
||||
switch (this) {
|
||||
case ViewLayoutPB.Grid:
|
||||
return LocaleKeys.document_slashMenu_name_grid.tr();
|
||||
case ViewLayoutPB.Board:
|
||||
return LocaleKeys.document_slashMenu_name_kanban.tr();
|
||||
case ViewLayoutPB.Document:
|
||||
return LocaleKeys.document_slashMenu_name_doc.tr();
|
||||
case ViewLayoutPB.Calendar:
|
||||
return LocaleKeys.document_slashMenu_name_calendar.tr();
|
||||
default:
|
||||
throw UnsupportedError('Unsupported layout: $this');
|
||||
}
|
||||
}
|
||||
|
||||
String get slashMenuLinkedName {
|
||||
switch (this) {
|
||||
case ViewLayoutPB.Grid:
|
||||
return LocaleKeys.document_slashMenu_name_linkedGrid.tr();
|
||||
case ViewLayoutPB.Board:
|
||||
return LocaleKeys.document_slashMenu_name_linkedKanban.tr();
|
||||
case ViewLayoutPB.Document:
|
||||
return LocaleKeys.document_slashMenu_name_linkedDoc.tr();
|
||||
case ViewLayoutPB.Calendar:
|
||||
return LocaleKeys.document_slashMenu_name_linkedCalendar.tr();
|
||||
default:
|
||||
throw UnsupportedError('Unsupported layout: $this');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,7 @@ import 'package:appflowy/plugins/database/tab_bar/desktop/tab_bar_header.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/media.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';
|
||||
@@ -50,6 +51,7 @@ import 'package:appflowy/plugins/database/widgets/cell_editor/checklist_cell_edi
|
||||
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/media_cell_editor.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell_editor/select_option_cell_editor.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell_editor/select_option_text_field.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/database_layout_ext.dart';
|
||||
@@ -72,7 +74,6 @@ import 'package:appflowy/util/field_type_extension.dart';
|
||||
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/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/protobuf.dart';
|
||||
@@ -474,7 +475,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
await pumpAndSettle();
|
||||
if (enter) {
|
||||
await testTextInput.receiveAction(TextInputAction.done);
|
||||
await pumpAndSettle();
|
||||
await pumpAndSettle(const Duration(milliseconds: 500));
|
||||
} else {
|
||||
await tapButton(
|
||||
find.descendant(
|
||||
@@ -627,12 +628,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
(w) => w is FieldActionCell && w.action == FieldAction.delete,
|
||||
);
|
||||
await tapButton(deleteButton);
|
||||
|
||||
final confirmButton = find.descendant(
|
||||
of: find.byType(NavigatorAlertDialog),
|
||||
matching: find.byType(PrimaryTextButton),
|
||||
);
|
||||
await tapButton(confirmButton);
|
||||
await tapButtonWithName(LocaleKeys.space_delete.tr());
|
||||
}
|
||||
|
||||
Future<void> scrollRowDetailByOffset(Offset offset) async {
|
||||
@@ -786,7 +782,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
|
||||
/// 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 {
|
||||
void findCellByFieldType(FieldType fieldType) {
|
||||
final finder = finderForFieldType(fieldType);
|
||||
expect(finder, findsWidgets);
|
||||
}
|
||||
@@ -857,6 +853,11 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
expect(finder, matcher);
|
||||
}
|
||||
|
||||
Future<void> findMediaCellEditor(dynamic matcher) async {
|
||||
final finder = find.byType(MediaCellEditor);
|
||||
expect(finder, matcher);
|
||||
}
|
||||
|
||||
Future<void> findSelectOptionEditor(dynamic matcher) async {
|
||||
final finder = find.byType(SelectOptionCellEditor);
|
||||
expect(finder, matcher);
|
||||
@@ -887,18 +888,19 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
}
|
||||
|
||||
Future<void> createField(
|
||||
FieldType fieldType,
|
||||
String name, {
|
||||
FieldType fieldType, {
|
||||
String? name,
|
||||
ViewLayoutPB layout = ViewLayoutPB.Grid,
|
||||
}) async {
|
||||
if (layout == ViewLayoutPB.Grid) {
|
||||
await scrollToRight(find.byType(GridPage));
|
||||
}
|
||||
await tapNewPropertyButton();
|
||||
await renameField(name);
|
||||
if (name != null) {
|
||||
await renameField(name);
|
||||
}
|
||||
await tapSwitchFieldTypeButton();
|
||||
await selectFieldType(fieldType);
|
||||
await dismissFieldEditor();
|
||||
}
|
||||
|
||||
Future<void> tapDatabaseSettingButton() async {
|
||||
@@ -1580,6 +1582,8 @@ Finder finderForFieldType(FieldType fieldType) {
|
||||
return find.byType(EditableTextCell, skipOffstage: false);
|
||||
case FieldType.URL:
|
||||
return find.byType(EditableURLCell, skipOffstage: false);
|
||||
case FieldType.Media:
|
||||
return find.byType(EditableMediaCell, skipOffstage: false);
|
||||
default:
|
||||
throw Exception('Unknown field type: $fieldType');
|
||||
}
|
||||
|
||||
@@ -3,15 +3,15 @@ import 'dart:ui';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/base/emoji/emoji_picker.dart';
|
||||
import 'package:appflowy/plugins/base/emoji/emoji_skin_tone.dart';
|
||||
import 'package:appflowy/plugins/base/icon/icon_picker.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_add_button.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_option_button.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/cover_editor.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/embed_image_url_widget.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/embed_image_url_widget.dart';
|
||||
import 'package:appflowy/plugins/inline_actions/widgets/inline_actions_handler.dart';
|
||||
import 'package:appflowy/shared/icon_emoji_picker/emoji_skin_tone.dart';
|
||||
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -84,7 +84,7 @@ class EditorOperations {
|
||||
final Finder button = !isInPicker
|
||||
? find.text(LocaleKeys.document_plugins_cover_removeIcon.tr())
|
||||
: find.descendant(
|
||||
of: find.byType(FlowyIconPicker),
|
||||
of: find.byType(FlowyIconEmojiPicker),
|
||||
matching: find.text(LocaleKeys.button_remove.tr()),
|
||||
);
|
||||
await tester.tapButton(button);
|
||||
@@ -170,8 +170,26 @@ class EditorOperations {
|
||||
/// Tap the slash menu item with [name]
|
||||
///
|
||||
/// Must call [showSlashMenu] first.
|
||||
Future<void> tapSlashMenuItemWithName(String name) async {
|
||||
Future<void> tapSlashMenuItemWithName(
|
||||
String name, {
|
||||
double offset = 200,
|
||||
}) async {
|
||||
final slashMenu = find
|
||||
.ancestor(
|
||||
of: find.byType(SelectionMenuItemWidget),
|
||||
matching: find.byWidgetPredicate(
|
||||
(widget) => widget is Scrollable,
|
||||
),
|
||||
)
|
||||
.first;
|
||||
final slashMenuItem = find.text(name, findRichText: true);
|
||||
await tester.scrollUntilVisible(
|
||||
slashMenuItem,
|
||||
offset,
|
||||
scrollable: slashMenu,
|
||||
duration: const Duration(milliseconds: 250),
|
||||
);
|
||||
assert(slashMenuItem.hasFound);
|
||||
await tester.tapButton(slashMenuItem);
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ class MockOpenAIRepository extends HttpOpenAIRepository {
|
||||
required Future<void> Function() onStart,
|
||||
required Future<void> Function(TextCompletionResponse response) onProcess,
|
||||
required Future<void> Function() onEnd,
|
||||
required void Function(OpenAIError error) onError,
|
||||
required void Function(AIError error) onError,
|
||||
String? suffix,
|
||||
int maxTokens = 2048,
|
||||
double temperature = 0.3,
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/shared/sidebar_setting.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/account/account_user_profile.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_workspace_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/settings_dialog.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu_element.dart';
|
||||
@@ -12,17 +12,23 @@ import 'package:flowy_infra_ui/style_widget/text_field.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../desktop/board/board_hide_groups_test.dart';
|
||||
|
||||
import 'base.dart';
|
||||
import 'common_operations.dart';
|
||||
|
||||
extension AppFlowySettings on WidgetTester {
|
||||
/// Open settings page
|
||||
Future<void> openSettings() async {
|
||||
final settingsDialog = find.byType(SettingsDialog);
|
||||
// tap empty area to close the settings page
|
||||
while (settingsDialog.evaluate().isNotEmpty) {
|
||||
await tapAt(Offset.zero);
|
||||
await pumpAndSettle();
|
||||
}
|
||||
|
||||
final settingsButton = find.byType(UserSettingButton);
|
||||
expect(settingsButton, findsOneWidget);
|
||||
await tapButton(settingsButton);
|
||||
final settingsDialog = find.byType(SettingsDialog);
|
||||
|
||||
expect(settingsDialog, findsOneWidget);
|
||||
return;
|
||||
}
|
||||
@@ -71,14 +77,14 @@ extension AppFlowySettings on WidgetTester {
|
||||
Future<void> enterUserName(String name) async {
|
||||
// Enable editing username
|
||||
final editUsernameFinder = find.descendant(
|
||||
of: find.byType(UserProfileSetting),
|
||||
of: find.byType(AccountUserProfile),
|
||||
matching: find.byFlowySvg(FlowySvgs.edit_s),
|
||||
);
|
||||
await tap(editUsernameFinder);
|
||||
await tap(editUsernameFinder, warnIfMissed: false);
|
||||
await pumpAndSettle();
|
||||
|
||||
final userNameFinder = find.descendant(
|
||||
of: find.byType(UserProfileSetting),
|
||||
of: find.byType(AccountUserProfile),
|
||||
matching: find.byType(FlowyTextField),
|
||||
);
|
||||
await enterText(userNameFinder, name);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/base/icon/icon_picker.dart';
|
||||
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_icon.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart';
|
||||
@@ -58,7 +58,7 @@ extension AppFlowyWorkspace on WidgetTester {
|
||||
);
|
||||
expect(iconButton, findsOneWidget);
|
||||
await tapButton(iconButton);
|
||||
final iconPicker = find.byType(FlowyIconPicker);
|
||||
final iconPicker = find.byType(FlowyIconEmojiPicker);
|
||||
expect(iconPicker, findsOneWidget);
|
||||
await tapButton(find.findTextInFlowyText(icon));
|
||||
}
|
||||
|
||||
@@ -48,8 +48,6 @@ PODS:
|
||||
- fluttertoast (0.0.2):
|
||||
- Flutter
|
||||
- Toast
|
||||
- image_gallery_saver (2.0.2):
|
||||
- Flutter
|
||||
- image_picker_ios (0.0.1):
|
||||
- Flutter
|
||||
- integration_test (0.0.1):
|
||||
@@ -58,6 +56,8 @@ PODS:
|
||||
- Flutter
|
||||
- keyboard_height_plugin (0.0.1):
|
||||
- Flutter
|
||||
- open_filex (0.0.2):
|
||||
- Flutter
|
||||
- package_info_plus (0.4.5):
|
||||
- Flutter
|
||||
- path_provider_foundation (0.0.1):
|
||||
@@ -69,6 +69,11 @@ PODS:
|
||||
- SDWebImage (5.14.2):
|
||||
- SDWebImage/Core (= 5.14.2)
|
||||
- SDWebImage/Core (5.14.2)
|
||||
- Sentry/HybridSDK (8.35.1)
|
||||
- sentry_flutter (8.8.0):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- Sentry/HybridSDK (= 8.35.1)
|
||||
- share_plus (0.0.1):
|
||||
- Flutter
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
@@ -93,14 +98,15 @@ DEPENDENCIES:
|
||||
- flowy_infra_ui (from `.symlinks/plugins/flowy_infra_ui/ios`)
|
||||
- Flutter (from `Flutter`)
|
||||
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
||||
- image_gallery_saver (from `.symlinks/plugins/image_gallery_saver/ios`)
|
||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||
- integration_test (from `.symlinks/plugins/integration_test/ios`)
|
||||
- irondash_engine_context (from `.symlinks/plugins/irondash_engine_context/ios`)
|
||||
- keyboard_height_plugin (from `.symlinks/plugins/keyboard_height_plugin/ios`)
|
||||
- open_filex (from `.symlinks/plugins/open_filex/ios`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sqflite (from `.symlinks/plugins/sqflite/darwin`)
|
||||
@@ -113,6 +119,7 @@ SPEC REPOS:
|
||||
- DKPhotoGallery
|
||||
- ReachabilitySwift
|
||||
- SDWebImage
|
||||
- Sentry
|
||||
- SwiftyGif
|
||||
- Toast
|
||||
|
||||
@@ -133,8 +140,6 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter
|
||||
fluttertoast:
|
||||
:path: ".symlinks/plugins/fluttertoast/ios"
|
||||
image_gallery_saver:
|
||||
:path: ".symlinks/plugins/image_gallery_saver/ios"
|
||||
image_picker_ios:
|
||||
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||
integration_test:
|
||||
@@ -143,12 +148,16 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/irondash_engine_context/ios"
|
||||
keyboard_height_plugin:
|
||||
:path: ".symlinks/plugins/keyboard_height_plugin/ios"
|
||||
open_filex:
|
||||
:path: ".symlinks/plugins/open_filex/ios"
|
||||
package_info_plus:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
path_provider_foundation:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||
permission_handler_apple:
|
||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||
sentry_flutter:
|
||||
:path: ".symlinks/plugins/sentry_flutter/ios"
|
||||
share_plus:
|
||||
:path: ".symlinks/plugins/share_plus/ios"
|
||||
shared_preferences_foundation:
|
||||
@@ -170,17 +179,19 @@ SPEC CHECKSUMS:
|
||||
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
|
||||
flowy_infra_ui: 0455e1fa8c51885aa1437848e361e99419f34ebc
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
|
||||
image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb
|
||||
fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c
|
||||
image_picker_ios: 99dfe1854b4fa34d0364e74a78448a0151025425
|
||||
integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4
|
||||
irondash_engine_context: 3458bf979b90d616ffb8ae03a150bafe2e860cc9
|
||||
keyboard_height_plugin: 43fa8bba20fd5c4fdeed5076466b8b9d43cc6b86
|
||||
open_filex: 6e26e659846ec990262224a12ef1c528bb4edbe4
|
||||
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
|
||||
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
|
||||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||
SDWebImage: b9a731e1d6307f44ca703b3976d18c24ca561e84
|
||||
Sentry: 1fe34e9c2cbba1e347623610d26db121dcb569f1
|
||||
sentry_flutter: a39c2a2d67d5e5b9cb0b94a4985c76dd5b3fc737
|
||||
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
|
||||
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
|
||||
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
||||
|
||||
@@ -372,6 +372,7 @@
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = AppFlowy;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -383,6 +384,8 @@
|
||||
STRIP_STYLE = "non-global";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
@@ -511,6 +514,7 @@
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = AppFlowy;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -522,6 +526,8 @@
|
||||
STRIP_STYLE = "non-global";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -545,6 +551,7 @@
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = AppFlowy;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -556,6 +563,8 @@
|
||||
STRIP_STYLE = "non-global";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
|
||||
@@ -1,75 +1,73 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>AppFlowy requires access to the camera.</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>AppFlowy requires access to the photo library.</string>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true />
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleLocalizations</key>
|
||||
<array>
|
||||
<string>en</string>
|
||||
</array>
|
||||
<key>FLTEnableImpeller</key>
|
||||
<false />
|
||||
<key>CFBundleName</key>
|
||||
<string>AppFlowy</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string></string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>appflowy-flutter</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true />
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true />
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false />
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleLocalizations</key>
|
||||
<array>
|
||||
<string>en</string>
|
||||
</array>
|
||||
<key>CFBundleName</key>
|
||||
<string>AppFlowy</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
<key>CFBundleURLName</key>
|
||||
<string></string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>appflowy-flutter</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>FLTEnableImpeller</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>AppFlowy needs access to your photos to let you add images to your documents</string>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>UISupportsDocumentBrowser</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -4,5 +4,9 @@
|
||||
<dict>
|
||||
<key>aps-environment</key>
|
||||
<string>development</string>
|
||||
<key>com.apple.developer.applesignin</key>
|
||||
<array>
|
||||
<string>Default</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -18,6 +18,13 @@ class KVKeys {
|
||||
/// {'dx': 10.0, 'dy': 10.0}
|
||||
static const String windowPosition = 'windowPosition';
|
||||
|
||||
/// The key for saving the window status
|
||||
///
|
||||
/// The value is a json string with the following format:
|
||||
/// { 'windowMaximized': true }
|
||||
///
|
||||
static const String windowMaximized = 'windowMaximized';
|
||||
|
||||
static const String kDocumentAppearanceFontSize =
|
||||
'kDocumentAppearanceFontSize';
|
||||
static const String kDocumentAppearanceFontFamily =
|
||||
|
||||
@@ -3,16 +3,6 @@ import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/shared/window_title_bar.dart';
|
||||
import 'package:appflowy/util/theme_extension.dart';
|
||||
import 'package:appflowy/workspace/application/home/home_setting_bloc.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class CocoaWindowChannel {
|
||||
CocoaWindowChannel._();
|
||||
|
||||
@@ -40,11 +30,9 @@ class MoveWindowDetector extends StatefulWidget {
|
||||
const MoveWindowDetector({
|
||||
super.key,
|
||||
this.child,
|
||||
this.showTitleBar = false,
|
||||
});
|
||||
|
||||
final Widget? child;
|
||||
final bool showTitleBar;
|
||||
|
||||
@override
|
||||
MoveWindowDetectorState createState() => MoveWindowDetectorState();
|
||||
@@ -56,28 +44,10 @@ class MoveWindowDetectorState extends State<MoveWindowDetector> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!Platform.isMacOS && !Platform.isWindows) {
|
||||
if (!Platform.isMacOS) {
|
||||
return widget.child ?? const SizedBox.shrink();
|
||||
}
|
||||
|
||||
if (Platform.isWindows) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (widget.showTitleBar) ...[
|
||||
WindowTitleBar(
|
||||
leftChildren: [
|
||||
_buildToggleMenuButton(context),
|
||||
],
|
||||
),
|
||||
] else ...[
|
||||
const SizedBox(height: 5),
|
||||
],
|
||||
widget.child ?? const SizedBox.shrink(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
// https://stackoverflow.com/questions/52965799/flutter-gesturedetector-not-working-with-containers-in-stack
|
||||
behavior: HitTestBehavior.translucent,
|
||||
@@ -98,47 +68,4 @@ class MoveWindowDetectorState extends State<MoveWindowDetector> {
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildToggleMenuButton(BuildContext context) {
|
||||
if (!context.read<HomeSettingBloc>().state.isMenuCollapsed) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final color = Theme.of(context).isLightMode ? Colors.white : Colors.black;
|
||||
final textSpan = TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '${LocaleKeys.sideBar_openSidebar.tr()}\n',
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(color: color),
|
||||
),
|
||||
TextSpan(
|
||||
text: Platform.isMacOS ? '⌘+.' : 'Ctrl+\\',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(color: Theme.of(context).hintColor),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
return FlowyTooltip(
|
||||
richMessage: textSpan,
|
||||
child: Listener(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onPointerDown: (_) => context
|
||||
.read<HomeSettingBloc>()
|
||||
.add(const HomeSettingEvent.collapseMenu()),
|
||||
child: FlowyHover(
|
||||
child: Container(
|
||||
width: 24,
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: const RotatedBox(
|
||||
quarterTurns: 2,
|
||||
child: FlowySvg(FlowySvgs.hide_menu_s),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,6 @@ class AppFlowyConfiguration {
|
||||
required this.device_id,
|
||||
required this.platform,
|
||||
required this.authenticator_type,
|
||||
required this.supabase_config,
|
||||
required this.appflowy_cloud_config,
|
||||
required this.envs,
|
||||
});
|
||||
@@ -28,41 +27,12 @@ class AppFlowyConfiguration {
|
||||
final String device_id;
|
||||
final String platform;
|
||||
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 {
|
||||
SupabaseConfiguration({
|
||||
required this.url,
|
||||
required this.anon_key,
|
||||
});
|
||||
|
||||
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() {
|
||||
return SupabaseConfiguration(
|
||||
url: '',
|
||||
anon_key: '',
|
||||
);
|
||||
}
|
||||
|
||||
bool get isValid {
|
||||
return url.isNotEmpty && anon_key.isNotEmpty;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class AppFlowyCloudConfiguration {
|
||||
AppFlowyCloudConfiguration({
|
||||
|
||||
@@ -21,9 +21,6 @@ Future<void> _setAuthenticatorType(AuthenticatorType ty) async {
|
||||
case AuthenticatorType.local:
|
||||
await getIt<KeyValueStorage>().set(KVKeys.kCloudType, 0.toString());
|
||||
break;
|
||||
case AuthenticatorType.supabase:
|
||||
await getIt<KeyValueStorage>().set(KVKeys.kCloudType, 1.toString());
|
||||
break;
|
||||
case AuthenticatorType.appflowyCloud:
|
||||
await getIt<KeyValueStorage>().set(KVKeys.kCloudType, 2.toString());
|
||||
break;
|
||||
@@ -63,8 +60,6 @@ Future<AuthenticatorType> getAuthenticatorType() async {
|
||||
switch (value ?? "0") {
|
||||
case "0":
|
||||
return AuthenticatorType.local;
|
||||
case "1":
|
||||
return AuthenticatorType.supabase;
|
||||
case "2":
|
||||
return AuthenticatorType.appflowyCloud;
|
||||
case "3":
|
||||
@@ -93,10 +88,6 @@ Future<AuthenticatorType> getAuthenticatorType() async {
|
||||
/// Returns `false` otherwise.
|
||||
bool get isAuthEnabled {
|
||||
final env = getIt<AppFlowyCloudSharedEnv>();
|
||||
if (env.authenticatorType == AuthenticatorType.supabase) {
|
||||
return env.supabaseConfig.isValid;
|
||||
}
|
||||
|
||||
if (env.authenticatorType.isAppFlowyCloudEnabled) {
|
||||
return env.appflowyCloudConfig.isValid;
|
||||
}
|
||||
@@ -104,19 +95,6 @@ bool get isAuthEnabled {
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Checks if Supabase is enabled.
|
||||
///
|
||||
/// This getter evaluates if Supabase should be enabled based on the
|
||||
/// current integration mode and cloud type setting.
|
||||
///
|
||||
/// Returns:
|
||||
/// A boolean value indicating whether Supabase is enabled. It returns `true`
|
||||
/// if the application is in release or develop mode and the current cloud type
|
||||
/// is `CloudType.supabase`. Otherwise, it returns `false`.
|
||||
bool get isSupabaseEnabled {
|
||||
return currentCloudType().isSupabaseEnabled;
|
||||
}
|
||||
|
||||
/// Determines if AppFlowy Cloud is enabled.
|
||||
bool get isAppFlowyCloudEnabled {
|
||||
return currentCloudType().isAppFlowyCloudEnabled;
|
||||
@@ -124,7 +102,6 @@ bool get isAppFlowyCloudEnabled {
|
||||
|
||||
enum AuthenticatorType {
|
||||
local,
|
||||
supabase,
|
||||
appflowyCloud,
|
||||
appflowyCloudSelfHost,
|
||||
// The 'appflowyCloudDevelop' type is used for develop purposes only.
|
||||
@@ -137,14 +114,10 @@ enum AuthenticatorType {
|
||||
this == AuthenticatorType.appflowyCloudDevelop ||
|
||||
this == AuthenticatorType.appflowyCloud;
|
||||
|
||||
bool get isSupabaseEnabled => this == AuthenticatorType.supabase;
|
||||
|
||||
int get value {
|
||||
switch (this) {
|
||||
case AuthenticatorType.local:
|
||||
return 0;
|
||||
case AuthenticatorType.supabase:
|
||||
return 1;
|
||||
case AuthenticatorType.appflowyCloud:
|
||||
return 2;
|
||||
case AuthenticatorType.appflowyCloudSelfHost:
|
||||
@@ -158,8 +131,6 @@ enum AuthenticatorType {
|
||||
switch (value) {
|
||||
case 0:
|
||||
return AuthenticatorType.local;
|
||||
case 1:
|
||||
return AuthenticatorType.supabase;
|
||||
case 2:
|
||||
return AuthenticatorType.appflowyCloud;
|
||||
case 3:
|
||||
@@ -197,25 +168,15 @@ Future<void> useLocalServer() async {
|
||||
await _setAuthenticatorType(AuthenticatorType.local);
|
||||
}
|
||||
|
||||
Future<void> useSupabaseCloud({
|
||||
required String url,
|
||||
required String anonKey,
|
||||
}) async {
|
||||
await _setAuthenticatorType(AuthenticatorType.supabase);
|
||||
await setSupabaseServer(url, anonKey);
|
||||
}
|
||||
|
||||
/// Use getIt<AppFlowyCloudSharedEnv>() to get the shared environment.
|
||||
class AppFlowyCloudSharedEnv {
|
||||
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;
|
||||
|
||||
@@ -229,10 +190,6 @@ class AppFlowyCloudSharedEnv {
|
||||
? 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].
|
||||
// When the cloud type is [AuthenticatorType.appflowyCloudSelfHost] in the frontend, it should be
|
||||
@@ -244,7 +201,6 @@ class AppFlowyCloudSharedEnv {
|
||||
return AppFlowyCloudSharedEnv(
|
||||
authenticatorType: authenticatorType,
|
||||
appflowyCloudConfig: appflowyCloudConfig,
|
||||
supabaseConfig: supabaseCloudConfig,
|
||||
);
|
||||
} else {
|
||||
// Using the cloud settings from the .env file.
|
||||
@@ -257,7 +213,6 @@ class AppFlowyCloudSharedEnv {
|
||||
return AppFlowyCloudSharedEnv(
|
||||
authenticatorType: AuthenticatorType.fromValue(Env.authenticatorType),
|
||||
appflowyCloudConfig: appflowyCloudConfig,
|
||||
supabaseConfig: SupabaseConfiguration.defaultConfig(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -265,8 +220,7 @@ class AppFlowyCloudSharedEnv {
|
||||
@override
|
||||
String toString() {
|
||||
return 'authenticator: $_authenticatorType\n'
|
||||
'appflowy: ${appflowyCloudConfig.toJson()}\n'
|
||||
'supabase: ${supabaseConfig.toJson()})\n';
|
||||
'appflowy: ${appflowyCloudConfig.toJson()}\n';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,22 +308,3 @@ Future<void> setSupabaseServer(
|
||||
await getIt<KeyValueStorage>().set(KVKeys.kSupabaseAnonKey, anonKey);
|
||||
}
|
||||
}
|
||||
|
||||
Future<SupabaseConfiguration> getSupabaseCloudConfig() async {
|
||||
final url = await _getSupabaseUrl();
|
||||
final anonKey = await _getSupabaseAnonKey();
|
||||
return SupabaseConfiguration(
|
||||
url: url,
|
||||
anon_key: anonKey,
|
||||
);
|
||||
}
|
||||
|
||||
Future<String> _getSupabaseUrl() async {
|
||||
final result = await getIt<KeyValueStorage>().get(KVKeys.kSupabaseURL);
|
||||
return result ?? '';
|
||||
}
|
||||
|
||||
Future<String> _getSupabaseAnonKey() async {
|
||||
final result = await getIt<KeyValueStorage>().get(KVKeys.kSupabaseAnonKey);
|
||||
return result ?? '';
|
||||
}
|
||||
@@ -29,4 +29,18 @@ abstract class Env {
|
||||
defaultValue: '',
|
||||
)
|
||||
static const String afCloudUrl = _Env.afCloudUrl;
|
||||
|
||||
@EnviedField(
|
||||
obfuscate: false,
|
||||
varName: 'INTERNAL_BUILD',
|
||||
defaultValue: '',
|
||||
)
|
||||
static const String internalBuild = _Env.internalBuild;
|
||||
|
||||
@EnviedField(
|
||||
obfuscate: false,
|
||||
varName: 'SENTRY_DSN',
|
||||
defaultValue: '',
|
||||
)
|
||||
static const String sentryDsn = _Env.sentryDsn;
|
||||
}
|
||||
@@ -2,27 +2,41 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:appflowy/mobile/presentation/chat/mobile_chat_screen.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
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';
|
||||
import 'package:appflowy/mobile/presentation/presentation.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/application/recent/cached_recent_service.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
extension MobileRouter on BuildContext {
|
||||
Future<void> pushView(ViewPB view, [Map<String, dynamic>? arguments]) async {
|
||||
Future<void> pushView(
|
||||
ViewPB view, {
|
||||
Map<String, dynamic>? arguments,
|
||||
bool addInRecent = true,
|
||||
bool showMoreButton = true,
|
||||
String? fixedTitle,
|
||||
}) async {
|
||||
// set the current view before pushing the new view
|
||||
getIt<MenuSharedState>().latestOpenView = view;
|
||||
unawaited(getIt<CachedRecentService>().updateRecentViews([view.id], true));
|
||||
final queryParameters = view.queryParameters(arguments);
|
||||
|
||||
if (view.layout == ViewLayoutPB.Document) {
|
||||
queryParameters[MobileDocumentScreen.viewShowMoreButton] =
|
||||
showMoreButton.toString();
|
||||
if (fixedTitle != null) {
|
||||
queryParameters[MobileDocumentScreen.viewFixedTitle] = fixedTitle;
|
||||
}
|
||||
}
|
||||
|
||||
final uri = Uri(
|
||||
path: view.routeName,
|
||||
queryParameters: view.queryParameters(arguments),
|
||||
queryParameters: queryParameters,
|
||||
).toString();
|
||||
await push(uri);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_service.dart';
|
||||
import 'package:appflowy/user/application/reminder/reminder_extension.dart';
|
||||
import 'package:appflowy/workspace/application/settings/date_time/date_format_ext.dart';
|
||||
import 'package:appflowy/workspace/application/settings/date_time/time_format_ext.dart';
|
||||
import 'package:appflowy/workspace/application/view/prelude.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:time/time.dart';
|
||||
|
||||
part 'notification_reminder_bloc.freezed.dart';
|
||||
|
||||
class NotificationReminderBloc
|
||||
extends Bloc<NotificationReminderEvent, NotificationReminderState> {
|
||||
NotificationReminderBloc() : super(NotificationReminderState.initial()) {
|
||||
on<NotificationReminderEvent>((event, emit) async {
|
||||
await event.when(
|
||||
initial: (reminder, dateFormat, timeFormat) async {
|
||||
this.reminder = reminder;
|
||||
this.dateFormat = dateFormat;
|
||||
this.timeFormat = timeFormat;
|
||||
|
||||
add(const NotificationReminderEvent.reset());
|
||||
},
|
||||
reset: () async {
|
||||
final createdAt = await _getCreatedAt(
|
||||
reminder,
|
||||
dateFormat,
|
||||
timeFormat,
|
||||
);
|
||||
final view = await _getView(reminder);
|
||||
|
||||
if (view == null) {
|
||||
emit(
|
||||
NotificationReminderState(
|
||||
createdAt: createdAt,
|
||||
pageTitle: '',
|
||||
reminderContent: '',
|
||||
status: NotificationReminderStatus.error,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final layout = view!.layout;
|
||||
|
||||
if (layout.isDocumentView) {
|
||||
final node = await _getContent(reminder);
|
||||
if (node != null) {
|
||||
emit(
|
||||
NotificationReminderState(
|
||||
createdAt: createdAt,
|
||||
pageTitle: view.name,
|
||||
view: view,
|
||||
reminderContent: node.delta?.toPlainText() ?? '',
|
||||
nodes: [node],
|
||||
status: NotificationReminderStatus.loaded,
|
||||
),
|
||||
);
|
||||
}
|
||||
} else if (layout.isDatabaseView) {
|
||||
emit(
|
||||
NotificationReminderState(
|
||||
createdAt: createdAt,
|
||||
pageTitle: view.name,
|
||||
view: view,
|
||||
reminderContent: reminder.message,
|
||||
status: NotificationReminderStatus.loaded,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
late final ReminderPB reminder;
|
||||
late final UserDateFormatPB dateFormat;
|
||||
late final UserTimeFormatPB timeFormat;
|
||||
|
||||
Future<String> _getCreatedAt(
|
||||
ReminderPB reminder,
|
||||
UserDateFormatPB dateFormat,
|
||||
UserTimeFormatPB timeFormat,
|
||||
) async {
|
||||
final rCreatedAt = reminder.createdAt;
|
||||
final createdAt = rCreatedAt != null
|
||||
? _formatTimestamp(
|
||||
rCreatedAt,
|
||||
timeFormat: timeFormat,
|
||||
dateFormate: dateFormat,
|
||||
)
|
||||
: '';
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
Future<ViewPB?> _getView(ReminderPB reminder) async {
|
||||
return ViewBackendService.getView(reminder.objectId)
|
||||
.fold((s) => s, (_) => null);
|
||||
}
|
||||
|
||||
Future<Node?> _getContent(ReminderPB reminder) async {
|
||||
final blockId = reminder.meta[ReminderMetaKeys.blockId];
|
||||
|
||||
if (blockId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final document = await DocumentService()
|
||||
.openDocument(
|
||||
documentId: reminder.objectId,
|
||||
)
|
||||
.fold((s) => s.toDocument(), (_) => null);
|
||||
|
||||
if (document == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final node = _searchById(document.root, blockId);
|
||||
|
||||
if (node == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
Node? _searchById(Node current, String id) {
|
||||
if (current.id == id) {
|
||||
return current;
|
||||
}
|
||||
|
||||
if (current.children.isNotEmpty) {
|
||||
for (final child in current.children) {
|
||||
final node = _searchById(child, id);
|
||||
|
||||
if (node != null) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
String _formatTimestamp(
|
||||
int timestamp, {
|
||||
required UserDateFormatPB dateFormate,
|
||||
required UserTimeFormatPB timeFormat,
|
||||
}) {
|
||||
final now = DateTime.now();
|
||||
final dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp);
|
||||
final difference = now.difference(dateTime);
|
||||
final String date;
|
||||
|
||||
if (difference.inMinutes < 1) {
|
||||
date = LocaleKeys.sideBar_justNow.tr();
|
||||
} else if (difference.inHours < 1 && dateTime.isToday) {
|
||||
// Less than 1 hour
|
||||
date = LocaleKeys.sideBar_minutesAgo
|
||||
.tr(namedArgs: {'count': difference.inMinutes.toString()});
|
||||
} else if (difference.inHours >= 1 && dateTime.isToday) {
|
||||
// in same day
|
||||
date = timeFormat.formatTime(dateTime);
|
||||
} else {
|
||||
date = dateFormate.formatDate(dateTime, false);
|
||||
}
|
||||
|
||||
return date;
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class NotificationReminderEvent with _$NotificationReminderEvent {
|
||||
const factory NotificationReminderEvent.initial(
|
||||
ReminderPB reminder,
|
||||
UserDateFormatPB dateFormat,
|
||||
UserTimeFormatPB timeFormat,
|
||||
) = _Initial;
|
||||
|
||||
const factory NotificationReminderEvent.reset() = _Reset;
|
||||
}
|
||||
|
||||
enum NotificationReminderStatus {
|
||||
initial,
|
||||
loading,
|
||||
loaded,
|
||||
error,
|
||||
}
|
||||
|
||||
@freezed
|
||||
class NotificationReminderState with _$NotificationReminderState {
|
||||
const NotificationReminderState._();
|
||||
|
||||
const factory NotificationReminderState({
|
||||
required String createdAt,
|
||||
required String pageTitle,
|
||||
required String reminderContent,
|
||||
@Default(NotificationReminderStatus.initial)
|
||||
NotificationReminderStatus status,
|
||||
@Default([]) List<Node> nodes,
|
||||
ViewPB? view,
|
||||
}) = _NotificationReminderState;
|
||||
|
||||
factory NotificationReminderState.initial() =>
|
||||
const NotificationReminderState(
|
||||
createdAt: '',
|
||||
pageTitle: '',
|
||||
reminderContent: '',
|
||||
);
|
||||
}
|
||||
@@ -23,6 +23,9 @@ class DocumentPageStyleBloc
|
||||
await event.when(
|
||||
initial: () async {
|
||||
try {
|
||||
if (view.id.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final layoutObject =
|
||||
await ViewBackendService.getView(view.id).fold(
|
||||
(s) => jsonDecode(s.extra),
|
||||
@@ -146,7 +149,7 @@ class DocumentPageStyleBloc
|
||||
) {
|
||||
double padding = switch (fontLayout) {
|
||||
PageStyleFontLayout.small => 1.0,
|
||||
PageStyleFontLayout.normal => 2.0,
|
||||
PageStyleFontLayout.normal => 1.0,
|
||||
PageStyleFontLayout.large => 4.0,
|
||||
};
|
||||
switch (lineHeightLayout) {
|
||||
@@ -162,6 +165,16 @@ class DocumentPageStyleBloc
|
||||
return max(0, padding);
|
||||
}
|
||||
|
||||
double calculateIconScale(
|
||||
PageStyleFontLayout fontLayout,
|
||||
) {
|
||||
return switch (fontLayout) {
|
||||
PageStyleFontLayout.small => 0.8,
|
||||
PageStyleFontLayout.normal => 1.0,
|
||||
PageStyleFontLayout.large => 1.2,
|
||||
};
|
||||
}
|
||||
|
||||
PageStyleFontLayout _getSelectedFontLayout(Map layoutObject) {
|
||||
final fontLayout = layoutObject[ViewExtKeys.fontLayoutKey] ??
|
||||
PageStyleFontLayout.normal.toString();
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_listener.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_service.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy/workspace/application/view/prelude.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
@@ -113,7 +111,6 @@ class RecentViewBloc extends Bloc<RecentViewEvent, RecentViewState> {
|
||||
);
|
||||
}
|
||||
|
||||
final _service = DocumentService();
|
||||
final ViewPB view;
|
||||
final DocumentListener _documentListener;
|
||||
final ViewListener _viewListener;
|
||||
@@ -124,16 +121,6 @@ class RecentViewBloc extends Bloc<RecentViewEvent, RecentViewState> {
|
||||
|
||||
// for the version under 0.5.5
|
||||
Future<(CoverType, String?)> getCoverV1() async {
|
||||
final result = await _service.getDocument(documentId: view.id);
|
||||
final document = result.fold((s) => s.toDocument(), (f) => null);
|
||||
if (document != null) {
|
||||
final coverType = CoverType.fromString(
|
||||
document.root.attributes[DocumentHeaderBlockKeys.coverType],
|
||||
);
|
||||
final coverValue = document
|
||||
.root.attributes[DocumentHeaderBlockKeys.coverDetails] as String?;
|
||||
return (coverType, coverValue);
|
||||
}
|
||||
return (CoverType.none, null);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,12 +12,12 @@ class UserProfileBloc extends Bloc<UserProfileEvent, UserProfileState> {
|
||||
UserProfileBloc() : super(const _Initial()) {
|
||||
on<UserProfileEvent>((event, emit) async {
|
||||
await event.when(
|
||||
started: () async => _initalize(emit),
|
||||
started: () async => _initialize(emit),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _initalize(Emitter<UserProfileState> emit) async {
|
||||
Future<void> _initialize(Emitter<UserProfileState> emit) async {
|
||||
emit(const UserProfileState.loading());
|
||||
|
||||
final workspaceOrFailure =
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import 'package:appflowy/shared/feedback_gesture_detector.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AnimatedGestureDetector extends StatefulWidget {
|
||||
const AnimatedGestureDetector({
|
||||
super.key,
|
||||
this.scaleFactor = 0.98,
|
||||
this.feedback = true,
|
||||
this.duration = const Duration(milliseconds: 100),
|
||||
this.alignment = Alignment.center,
|
||||
this.behavior = HitTestBehavior.opaque,
|
||||
this.onTapUp,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
final double scaleFactor;
|
||||
final Duration duration;
|
||||
final Alignment alignment;
|
||||
final bool feedback;
|
||||
final HitTestBehavior behavior;
|
||||
final VoidCallback? onTapUp;
|
||||
|
||||
@override
|
||||
State<AnimatedGestureDetector> createState() =>
|
||||
_AnimatedGestureDetectorState();
|
||||
}
|
||||
|
||||
class _AnimatedGestureDetectorState extends State<AnimatedGestureDetector> {
|
||||
double scale = 1.0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
behavior: widget.behavior,
|
||||
onTapUp: (details) {
|
||||
setState(() => scale = 1.0);
|
||||
|
||||
HapticFeedbackType.light.call();
|
||||
|
||||
widget.onTapUp?.call();
|
||||
},
|
||||
onTapDown: (details) {
|
||||
setState(() => scale = widget.scaleFactor);
|
||||
},
|
||||
child: AnimatedScale(
|
||||
scale: scale,
|
||||
alignment: widget.alignment,
|
||||
duration: widget.duration,
|
||||
child: widget.child,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ enum FlowyAppBarLeadingType {
|
||||
Widget getWidget(VoidCallback? onTap) {
|
||||
switch (this) {
|
||||
case FlowyAppBarLeadingType.back:
|
||||
return AppBarBackButton(onTap: onTap);
|
||||
return AppBarImmersiveBackButton(onTap: onTap);
|
||||
case FlowyAppBarLeadingType.close:
|
||||
return AppBarCloseButton(onTap: onTap);
|
||||
case FlowyAppBarLeadingType.cancel:
|
||||
|
||||
@@ -26,6 +26,31 @@ class AppBarBackButton extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class AppBarImmersiveBackButton extends StatelessWidget {
|
||||
const AppBarImmersiveBackButton({
|
||||
super.key,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
final VoidCallback? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppBarButton(
|
||||
onTap: (_) => (onTap ?? () => Navigator.pop(context)).call(),
|
||||
padding: const EdgeInsets.only(
|
||||
left: 12.0,
|
||||
top: 8.0,
|
||||
bottom: 8.0,
|
||||
right: 4.0,
|
||||
),
|
||||
child: const FlowySvg(
|
||||
FlowySvgs.m_app_bar_back_s,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AppBarCloseButton extends StatelessWidget {
|
||||
const AppBarCloseButton({
|
||||
super.key,
|
||||
|
||||
@@ -3,8 +3,8 @@ import 'package:appflowy/mobile/application/base/mobile_view_page_bloc.dart';
|
||||
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/app_bar/app_bar.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/view_page/app_bar_buttons.dart';
|
||||
import 'package:appflowy/mobile/presentation/presentation.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/presentation/document_collaborators.dart';
|
||||
import 'package:appflowy/shared/feature_flags.dart';
|
||||
import 'package:appflowy/startup/plugin/plugin.dart';
|
||||
@@ -27,6 +27,8 @@ class MobileViewPage extends StatefulWidget {
|
||||
required this.viewLayout,
|
||||
this.title,
|
||||
this.arguments,
|
||||
this.fixedTitle,
|
||||
this.showMoreButton = true,
|
||||
});
|
||||
|
||||
/// view id
|
||||
@@ -34,6 +36,10 @@ class MobileViewPage extends StatefulWidget {
|
||||
final ViewLayoutPB viewLayout;
|
||||
final String? title;
|
||||
final Map<String, dynamic>? arguments;
|
||||
final bool showMoreButton;
|
||||
|
||||
// only used in row page
|
||||
final String? fixedTitle;
|
||||
|
||||
@override
|
||||
State<MobileViewPage> createState() => _MobileViewPageState();
|
||||
@@ -46,10 +52,18 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
||||
// control the app bar opacity when in immersive mode
|
||||
final ValueNotifier<double> _appBarOpacity = ValueNotifier(1.0);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
getIt<ReminderBloc>().add(const ReminderEvent.started());
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_appBarOpacity.dispose();
|
||||
_scrollNotificationObserver = null;
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -78,8 +92,7 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
||||
ViewBloc(view: view)..add(const ViewEvent.initial()),
|
||||
),
|
||||
BlocProvider.value(
|
||||
value: getIt<ReminderBloc>()
|
||||
..add(const ReminderEvent.started()),
|
||||
value: getIt<ReminderBloc>(),
|
||||
),
|
||||
if (view.layout.isDocumentView)
|
||||
BlocProvider(
|
||||
@@ -125,7 +138,7 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
||||
return child;
|
||||
},
|
||||
)
|
||||
: child;
|
||||
: SafeArea(child: child);
|
||||
return Scaffold(
|
||||
extendBodyBehindAppBar: isDocument,
|
||||
appBar: appBar,
|
||||
@@ -157,6 +170,9 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
||||
return plugin.widgetBuilder.buildWidget(
|
||||
shrinkWrap: false,
|
||||
context: PluginContext(userProfile: state.userProfilePB),
|
||||
data: {
|
||||
MobileDocumentScreen.viewFixedTitle: widget.fixedTitle,
|
||||
},
|
||||
);
|
||||
},
|
||||
(error) {
|
||||
@@ -209,13 +225,19 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
||||
]);
|
||||
}
|
||||
|
||||
actions.addAll([
|
||||
MobileViewPageMoreButton(
|
||||
view: view,
|
||||
isImmersiveMode: isImmersiveMode,
|
||||
appBarOpacity: _appBarOpacity,
|
||||
),
|
||||
]);
|
||||
if (widget.showMoreButton) {
|
||||
actions.addAll([
|
||||
MobileViewPageMoreButton(
|
||||
view: view,
|
||||
isImmersiveMode: isImmersiveMode,
|
||||
appBarOpacity: _appBarOpacity,
|
||||
),
|
||||
]);
|
||||
} else {
|
||||
actions.addAll([
|
||||
const HSpace(18.0),
|
||||
]);
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
@@ -225,19 +247,20 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (icon != null && icon.isNotEmpty)
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints.tightFor(width: 34.0),
|
||||
child: EmojiText(
|
||||
emoji: '$icon ',
|
||||
fontSize: 22.0,
|
||||
),
|
||||
if (icon != null && icon.isNotEmpty) ...[
|
||||
FlowyText.emoji(
|
||||
icon,
|
||||
fontSize: 15.0,
|
||||
figmaLineHeight: 18.0,
|
||||
),
|
||||
const HSpace(4),
|
||||
],
|
||||
Expanded(
|
||||
child: FlowyText.medium(
|
||||
view?.name ?? widget.title ?? '',
|
||||
widget.fixedTitle ?? view?.name ?? widget.title ?? '',
|
||||
fontSize: 15.0,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
figmaLineHeight: 18.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||