Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
7586a0ed48 | ||
|
|
d8b32168a9 | ||
|
|
8ca541bfa1 | ||
|
|
b64da2c02f | ||
|
|
fdaca36b87 | ||
|
|
8557383a89 | ||
|
|
fa86480458 | ||
|
|
d75eb05707 | ||
|
|
a2b32585a1 | ||
|
|
75cea400d2 | ||
|
|
ed82ec8eef | ||
|
|
b6debd6c71 | ||
|
|
a05ed63a3e | ||
|
|
3e75f1f24a | ||
|
|
e607694729 | ||
|
|
91d870415e | ||
|
|
21a497924c | ||
|
|
4a126e17ce | ||
|
|
8bf97ad5c6 | ||
|
|
a85d11fb6d | ||
|
|
1fd34bc58a | ||
|
|
70a7b856a2 | ||
|
|
49683e6079 | ||
|
|
13107b63e9 | ||
|
|
ce1a6e4fca | ||
|
|
1d23e28eaf | ||
|
|
2a4d78b688 | ||
|
|
cbe452a73d | ||
|
|
27899ee993 | ||
|
|
785597f53e | ||
|
|
e2ce274718 | ||
|
|
6be9c00127 | ||
|
|
aa621a8d84 | ||
|
|
2d4300e931 | ||
|
|
dc12938ab6 | ||
|
|
2d674060c6 | ||
|
|
ecca81f3b8 | ||
|
|
15329b39d0 | ||
|
|
bd5f5f8b9e | ||
|
|
4f4be7eac7 | ||
|
|
f1a4deb459 | ||
|
|
4708c0f779 | ||
|
|
3d7a500550 | ||
|
|
815c99710e | ||
|
|
c9fe93920f | ||
|
|
5012b99c9b | ||
|
|
6b46372aa0 | ||
|
|
b6d873db1b | ||
|
|
38d2bd7ee8 | ||
|
|
bb3e9d5bd8 | ||
|
|
94060a0a99 | ||
|
|
86696b271e | ||
|
|
76fde00cc4 | ||
|
|
b794f3894e | ||
|
|
3b72f90ca5 | ||
|
|
d73e388d01 | ||
|
|
383e06ff2d | ||
|
|
ff93fbf0e6 | ||
|
|
bd7977d8ba | ||
|
|
6e7d044208 | ||
|
|
d8ca693976 | ||
|
|
52e7fb49cd | ||
|
|
f6e33a59d5 | ||
|
|
a0e981ec6b | ||
|
|
041e3c155a | ||
|
|
e4eff7e632 | ||
|
|
9f66dcdc8f | ||
|
|
890ac5a213 | ||
|
|
1cecfae6f4 | ||
|
|
1757570337 | ||
|
|
57d4652824 | ||
|
|
1e485188eb | ||
|
|
03e8dba5f3 | ||
|
|
d5cfd054cc | ||
|
|
9c9168ac5b | ||
|
|
4762d69851 | ||
|
|
aec7bc847e | ||
|
|
4d42c9ea68 | ||
|
|
0d64aa4311 | ||
|
|
2a59579c17 | ||
|
|
4ad7c48b25 | ||
|
|
2c0cdfa6c5 | ||
|
|
8b04506ac4 | ||
|
|
a1395cb54a | ||
|
|
526de2eb5c | ||
|
|
e40e1e9a8a | ||
|
|
ace729eb78 | ||
|
|
189f0e4b58 | ||
|
|
e8afcbb828 | ||
|
|
42e83b3ce9 | ||
|
|
859eaf903b | ||
|
|
0bfe071caf | ||
|
|
b8b7a10b33 | ||
|
|
cb44a885a1 | ||
|
|
083be32fa6 | ||
|
|
8b6575d1ee | ||
|
|
a8f136eda2 | ||
|
|
13b3439bd6 | ||
|
|
13a35672ab | ||
|
|
857b3aa106 | ||
|
|
a0139dd475 | ||
|
|
9a5dbbb3ce | ||
|
|
acae34836e | ||
|
|
68c4e19f91 |
No files matched your search
@@ -63,7 +63,7 @@ runs:
|
||||
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
|
||||
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev
|
||||
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev libmpv-dev mpv
|
||||
elif [ "$RUNNER_OS" == "Windows" ]; then
|
||||
vcpkg integrate install
|
||||
elif [ "$RUNNER_OS" == "macOS" ]; then
|
||||
|
||||
@@ -52,7 +52,7 @@ runs:
|
||||
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
|
||||
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev network-manager
|
||||
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev network-manager libmpv-dev mpv
|
||||
shell: bash
|
||||
|
||||
- name: Enable Flutter Desktop
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
# env:
|
||||
# CARGO_TERM_COLOR: always
|
||||
# FLUTTER_VERSION: "3.19.0"
|
||||
# FLUTTER_VERSION: "3.22.0"
|
||||
# RUST_TOOLCHAIN: "1.77.2"
|
||||
# CARGO_MAKE_VERSION: "0.36.6"
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
name: Build Bot
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
dispatch_slash_command:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# get build name from pubspec.yaml
|
||||
- name: Get build version
|
||||
working-directory: frontend/appflowy_flutter
|
||||
id: get_build_name
|
||||
run: |
|
||||
echo "fetching version from pubspec.yaml..."
|
||||
echo "build_name=$(grep 'version: ' pubspec.yaml | awk '{print $2}')" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: peter-evans/slash-command-dispatch@v4
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
commands: build
|
||||
static-args: |
|
||||
ref=refs/pull/${{ github.event.issue.number }}/head
|
||||
build_name=${{ steps.get_build_name.outputs.build_name }}
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
with:
|
||||
SSH_PRIVATE_KEY: ${{ env.SSH_PRIVATE_KEY }}
|
||||
ARGS: "-rlgoDzvc -i"
|
||||
SOURCE: "frontend/appflowy_web_app/dist frontend/appflowy_web_app/Dockerfile frontend/appflowy_web_app/nginx.conf frontend/appflowy_web_app/.env nginx-signed.crt nginx-signed.key"
|
||||
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/"
|
||||
|
||||
@@ -25,7 +25,7 @@ on:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
FLUTTER_VERSION: "3.19.0"
|
||||
FLUTTER_VERSION: "3.22.0"
|
||||
RUST_TOOLCHAIN: "1.77.2"
|
||||
CARGO_MAKE_VERSION: "0.36.6"
|
||||
|
||||
@@ -89,6 +89,7 @@ jobs:
|
||||
with:
|
||||
os: ${{ matrix.os }}
|
||||
flutter_version: ${{ env.FLUTTER_VERSION }}
|
||||
DISABLE_CI_TEST_LOG: "true"
|
||||
rust_toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
cargo_make_version: ${{ env.CARGO_MAKE_VERSION }}
|
||||
rust_target: ${{ matrix.target }}
|
||||
@@ -172,7 +173,7 @@ jobs:
|
||||
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
|
||||
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev
|
||||
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev libmpv-dev mpv
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
@@ -202,6 +203,7 @@ jobs:
|
||||
- name: Run Flutter unit tests
|
||||
env:
|
||||
DISABLE_EVENT_LOG: true
|
||||
DISABLE_CI_TEST_LOG: "true"
|
||||
working-directory: frontend
|
||||
run: |
|
||||
if [ "$RUNNER_OS" == "macOS" ]; then
|
||||
@@ -272,7 +274,7 @@ jobs:
|
||||
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
|
||||
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev
|
||||
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev libmpv-dev mpv
|
||||
shell: bash
|
||||
|
||||
- name: Enable Flutter Desktop
|
||||
@@ -319,6 +321,12 @@ jobs:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install video dependency
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install libmpv-dev mpv
|
||||
shell: bash
|
||||
|
||||
- name: Flutter Integration Test 1
|
||||
uses: ./.github/actions/flutter_integration_test
|
||||
with:
|
||||
@@ -343,6 +351,12 @@ jobs:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install video dependency
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install libmpv-dev mpv
|
||||
shell: bash
|
||||
|
||||
- name: Flutter Integration Test 2
|
||||
uses: ./.github/actions/flutter_integration_test
|
||||
with:
|
||||
@@ -367,6 +381,12 @@ jobs:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install video dependency
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install libmpv-dev mpv
|
||||
shell: bash
|
||||
|
||||
- name: Flutter Integration Test 3
|
||||
uses: ./.github/actions/flutter_integration_test
|
||||
with:
|
||||
|
||||
@@ -20,7 +20,7 @@ on:
|
||||
- "!frontend/appflowy_web_app/**"
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.19.0"
|
||||
FLUTTER_VERSION: "3.22.0"
|
||||
RUST_TOOLCHAIN: "1.77.2"
|
||||
|
||||
concurrency:
|
||||
|
||||
@@ -6,7 +6,7 @@ on:
|
||||
- "*"
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.19.0"
|
||||
FLUTTER_VERSION: "3.22.0"
|
||||
RUST_TOOLCHAIN: "1.77.2"
|
||||
|
||||
jobs:
|
||||
@@ -367,8 +367,8 @@ jobs:
|
||||
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential libsqlite3-dev libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev
|
||||
sudo apt-get install keybinder-3.0 libnotify-dev
|
||||
sudo apt-get -y install alien
|
||||
sudo apt-get install keybinder-3.0
|
||||
sudo apt-get install -y alien libnotify-dev libmpv-dev mpv
|
||||
source $HOME/.cargo/env
|
||||
cargo install --force cargo-make
|
||||
cargo install --force duckscript_cli
|
||||
|
||||
@@ -32,14 +32,14 @@ jobs:
|
||||
# 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
|
||||
# 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
|
||||
|
||||
@@ -10,7 +10,7 @@ on:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
FLUTTER_VERSION: "3.19.0"
|
||||
FLUTTER_VERSION: "3.22.0"
|
||||
RUST_TOOLCHAIN: "1.77.2"
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -52,11 +52,11 @@ jobs:
|
||||
working-directory: frontend/appflowy_web_app
|
||||
run: |
|
||||
pnpm install
|
||||
- name: test and lint
|
||||
- name: Run lint check
|
||||
working-directory: frontend/appflowy_web_app
|
||||
run: |
|
||||
pnpm run lint
|
||||
pnpm run test:unit
|
||||
|
||||
- name: build and analyze
|
||||
working-directory: frontend/appflowy_web_app
|
||||
run: |
|
||||
|
||||
@@ -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,4 @@
|
||||
name: Cypress Tests
|
||||
name: Web Code Coverage
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -6,6 +6,7 @@ on:
|
||||
- ".github/workflows/web2_ci.yaml"
|
||||
- "frontend/appflowy_web_app/**"
|
||||
- "frontend/resources/**"
|
||||
|
||||
env:
|
||||
NODE_VERSION: "18.16.0"
|
||||
PNPM_VERSION: "8.5.0"
|
||||
@@ -13,7 +14,7 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
cypress-run:
|
||||
test:
|
||||
if: github.event.pull_request.draft != true
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
@@ -45,4 +46,20 @@ jobs:
|
||||
component: true
|
||||
build: pnpm run build
|
||||
start: pnpm run start
|
||||
browser: chrome
|
||||
browser: chrome
|
||||
|
||||
- name: Jest run
|
||||
working-directory: frontend/appflowy_web_app
|
||||
run: |
|
||||
pnpm run test:unit
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v2
|
||||
with:
|
||||
token: cf9245e0-e136-4e21-b0ee-35755fa0c493
|
||||
files: frontend/appflowy_web_app/coverage/jest/lcov.info,frontend/appflowy_web_app/coverage/cypress/lcov.info
|
||||
flags: appflowy_web_app
|
||||
name: frontend/appflowy_web_app
|
||||
fail_ci_if_error: true
|
||||
verbose: true
|
||||
|
||||
+34
-1
@@ -1,4 +1,37 @@
|
||||
# Release Notes
|
||||
## 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.
|
||||
### Bug Fixes
|
||||
- Resolved shortcut conflicts on the board page.
|
||||
- Resolved an issue where underscores could cause the editor to freeze.
|
||||
|
||||
## Version 0.5.9 - 06/06/2024
|
||||
### New Features
|
||||
- Revamped the sidebar for both Desktop and Mobile.
|
||||
- Added support for embedding videos in documents.
|
||||
- Introduced a hotkey (Cmd/Ctrl + 0) to reset the app scale.
|
||||
- Supported searching the workspace by page title.
|
||||
### Bug Fixes
|
||||
- Fixed the issue preventing the use of Backspace to delete words in Kanban boards.
|
||||
|
||||
## Version 0.5.8 - 05/20/2024
|
||||
### New Features
|
||||
- Improvement to the Callout block to insert new lines
|
||||
@@ -105,7 +138,7 @@
|
||||
- Fixed a bug where newly created rows were not being automatically sorted.
|
||||
- Fixed issues related to deleting a sorting field or sort not removing existing sorts properly.
|
||||
### Notes
|
||||
- Windows 7, Windows 8, and iOS 11 are not yet supported due to the upgrade to Flutter 3.19.0.
|
||||
- Windows 7, Windows 8, and iOS 11 are not yet supported due to the upgrade to Flutter 3.22.0.
|
||||
|
||||
## Version 0.4.9 - 02/17/2024
|
||||
### Bug Fixes
|
||||
|
||||
@@ -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.5.8"
|
||||
APPFLOWY_VERSION = "0.6.2"
|
||||
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,7 +52,7 @@ android {
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "io.appflowy.appflowy"
|
||||
minSdkVersion 23
|
||||
minSdkVersion 24
|
||||
targetSdkVersion 33
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
|
||||
@@ -58,4 +58,11 @@
|
||||
<action android:name="android.support.customtabs.action.CustomTabsService" />
|
||||
</intent>
|
||||
</queries>
|
||||
<!--
|
||||
Media access permissions.
|
||||
Android 13 or higher.
|
||||
Used for VideoBlock (edia_kit)
|
||||
-->
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||
</manifest>
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'anon_user_continue_test.dart' as anon_user_continue_test;
|
||||
import 'appflowy_cloud_auth_test.dart' as appflowy_cloud_auth_test;
|
||||
import 'empty_test.dart' as preset_af_cloud_env_test;
|
||||
// import 'document_sync_test.dart' as document_sync_test;
|
||||
import 'user_setting_sync_test.dart' as user_sync_test;
|
||||
import 'workspace/change_name_and_icon_test.dart'
|
||||
as change_workspace_name_and_icon_test;
|
||||
@@ -10,13 +9,8 @@ import 'workspace/collaborative_workspace_test.dart'
|
||||
|
||||
Future<void> main() async {
|
||||
preset_af_cloud_env_test.main();
|
||||
|
||||
appflowy_cloud_auth_test.main();
|
||||
|
||||
// document_sync_test.main();
|
||||
|
||||
user_sync_test.main();
|
||||
|
||||
anon_user_continue_test.main();
|
||||
|
||||
// workspace
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
+51
-52
@@ -2,8 +2,6 @@
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/loading.dart';
|
||||
@@ -12,14 +10,15 @@ 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/home/menu/sidebar/sidebar_workspace.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_menu.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/user_avatar.dart';
|
||||
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:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
@@ -33,69 +32,69 @@ import '../../shared/util.dart';
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
final email = '${uuid()}@appflowy.io';
|
||||
// final email = '${uuid()}@appflowy.io';
|
||||
|
||||
group('collaborative workspace', () {
|
||||
// combine the create and delete workspace test to reduce the time
|
||||
testWidgets('create a new workspace, open it and then delete it',
|
||||
(tester) async {
|
||||
// only run the test when the feature flag is on
|
||||
if (!FeatureFlag.collaborativeWorkspace.isOn) {
|
||||
return;
|
||||
}
|
||||
// if (!FeatureFlag.collaborativeWorkspace.isOn) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
email: email,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
// await tester.initializeAppFlowy(
|
||||
// cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
// email: email,
|
||||
// );
|
||||
// await tester.tapGoogleLoginInButton();
|
||||
// await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
const name = 'AppFlowy.IO';
|
||||
// the workspace will be opened after created
|
||||
await tester.createCollaborativeWorkspace(name);
|
||||
// const name = 'AppFlowy.IO';
|
||||
// // the workspace will be opened after created
|
||||
// await tester.createCollaborativeWorkspace(name);
|
||||
|
||||
final loading = find.byType(Loading);
|
||||
await tester.pumpUntilNotFound(loading);
|
||||
// final loading = find.byType(Loading);
|
||||
// await tester.pumpUntilNotFound(loading);
|
||||
|
||||
Finder success;
|
||||
// Finder success;
|
||||
|
||||
final Finder items = find.byType(WorkspaceMenuItem);
|
||||
// final Finder items = find.byType(WorkspaceMenuItem);
|
||||
|
||||
// delete the newly created workspace
|
||||
await tester.openCollaborativeWorkspaceMenu();
|
||||
await tester.pumpUntilFound(items);
|
||||
// // delete the newly created workspace
|
||||
// await tester.openCollaborativeWorkspaceMenu();
|
||||
// await tester.pumpUntilFound(items);
|
||||
|
||||
expect(items, findsNWidgets(2));
|
||||
expect(
|
||||
tester.widget<WorkspaceMenuItem>(items.last).workspace.name,
|
||||
name,
|
||||
);
|
||||
// expect(items, findsNWidgets(2));
|
||||
// expect(
|
||||
// tester.widget<WorkspaceMenuItem>(items.last).workspace.name,
|
||||
// name,
|
||||
// );
|
||||
|
||||
final secondWorkspace = find.byType(WorkspaceMenuItem).last;
|
||||
await tester.hoverOnWidget(
|
||||
secondWorkspace,
|
||||
onHover: () async {
|
||||
// click the more button
|
||||
final moreButton = find.byType(WorkspaceMoreActionList);
|
||||
expect(moreButton, findsOneWidget);
|
||||
await tester.tapButton(moreButton);
|
||||
// click the delete button
|
||||
final deleteButton = find.text(LocaleKeys.button_delete.tr());
|
||||
expect(deleteButton, findsOneWidget);
|
||||
await tester.tapButton(deleteButton);
|
||||
// see the delete confirm dialog
|
||||
final confirm =
|
||||
find.text(LocaleKeys.workspace_deleteWorkspaceHintText.tr());
|
||||
expect(confirm, findsOneWidget);
|
||||
await tester.tapButton(find.text(LocaleKeys.button_ok.tr()));
|
||||
// delete success
|
||||
success = find.text(LocaleKeys.workspace_createSuccess.tr());
|
||||
await tester.pumpUntilFound(success);
|
||||
expect(success, findsOneWidget);
|
||||
await tester.pumpUntilNotFound(success);
|
||||
},
|
||||
);
|
||||
// final secondWorkspace = find.byType(WorkspaceMenuItem).last;
|
||||
// await tester.hoverOnWidget(
|
||||
// secondWorkspace,
|
||||
// onHover: () async {
|
||||
// // click the more button
|
||||
// final moreButton = find.byType(WorkspaceMoreActionList);
|
||||
// expect(moreButton, findsOneWidget);
|
||||
// await tester.tapButton(moreButton);
|
||||
// // click the delete button
|
||||
// final deleteButton = find.text(LocaleKeys.button_delete.tr());
|
||||
// expect(deleteButton, findsOneWidget);
|
||||
// await tester.tapButton(deleteButton);
|
||||
// // see the delete confirm dialog
|
||||
// final confirm =
|
||||
// find.text(LocaleKeys.workspace_deleteWorkspaceHintText.tr());
|
||||
// expect(confirm, findsOneWidget);
|
||||
// await tester.tapButton(find.text(LocaleKeys.button_ok.tr()));
|
||||
// // delete success
|
||||
// success = find.text(LocaleKeys.workspace_createSuccess.tr());
|
||||
// await tester.pumpUntilFound(success);
|
||||
// expect(success, findsOneWidget);
|
||||
// await tester.pumpUntilNotFound(success);
|
||||
// },
|
||||
// );
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -29,6 +29,7 @@ void main() {
|
||||
},
|
||||
);
|
||||
await tester.tapButtonWithName(LocaleKeys.button_delete.tr());
|
||||
await tester.tapOKButton();
|
||||
expect(find.text(name), findsNothing);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'board_row_test.dart' as board_row_test;
|
||||
import 'board_add_row_test.dart' as board_add_row_test;
|
||||
import 'board_group_test.dart' as board_group_test;
|
||||
import 'board_row_test.dart' as board_row_test;
|
||||
|
||||
void startTesting() {
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// Board integration tests
|
||||
|
||||
@@ -41,15 +41,16 @@ void main() {
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||
await tester.hoverOnFirstRowOfGrid();
|
||||
await tester.hoverOnFirstRowOfGrid(() async {
|
||||
// Open the row menu and then click the delete
|
||||
await tester.tapRowMenuButtonInGrid();
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tapDeleteOnRowMenu();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Open the row menu and then click the delete
|
||||
await tester.tapRowMenuButtonInGrid();
|
||||
await tester.tapDeleteOnRowMenu();
|
||||
|
||||
// 3 initial rows - 1 deleted
|
||||
await tester.assertNumberOfRowsInGridPage(2);
|
||||
await tester.pumpAndSettle();
|
||||
// 3 initial rows - 1 deleted
|
||||
await tester.assertNumberOfRowsInGridPage(2);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('check number of row indicator in the initial grid',
|
||||
|
||||
+3
@@ -66,6 +66,7 @@ void main() {
|
||||
LogicalKeyboardKey.keyR,
|
||||
],
|
||||
tester: tester,
|
||||
withKeyUp: true,
|
||||
);
|
||||
expect(first.attributes[blockComponentAlign], rightAlignmentKey);
|
||||
|
||||
@@ -77,6 +78,7 @@ void main() {
|
||||
LogicalKeyboardKey.keyE,
|
||||
],
|
||||
tester: tester,
|
||||
withKeyUp: true,
|
||||
);
|
||||
expect(first.attributes[blockComponentAlign], centerAlignmentKey);
|
||||
|
||||
@@ -88,6 +90,7 @@ void main() {
|
||||
LogicalKeyboardKey.keyL,
|
||||
],
|
||||
tester: tester,
|
||||
withKeyUp: true,
|
||||
);
|
||||
expect(first.attributes[blockComponentAlign], leftAlignmentKey);
|
||||
});
|
||||
|
||||
+68
-27
@@ -1,10 +1,11 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
@@ -116,6 +117,53 @@ 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 image(png) from memory', (tester) async {
|
||||
@@ -210,42 +258,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 +295,7 @@ extension on WidgetTester {
|
||||
Future<void> Function(EditorState editorState)? beforeTest,
|
||||
String? plainText,
|
||||
String? html,
|
||||
String? inAppJson,
|
||||
(String, Uint8List?)? image,
|
||||
}) async {
|
||||
await initializeAppFlowy();
|
||||
@@ -271,6 +311,7 @@ extension on WidgetTester {
|
||||
ClipboardServiceData(
|
||||
plainText: plainText,
|
||||
html: html,
|
||||
inAppJson: inAppJson,
|
||||
image: image,
|
||||
),
|
||||
);
|
||||
|
||||
+2
@@ -111,6 +111,7 @@ Future<void> triggerReferenceDocumentBySlashMenu(WidgetTester tester) async {
|
||||
LogicalKeyboardKey.enter,
|
||||
],
|
||||
tester: tester,
|
||||
withKeyUp: true,
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
@@ -129,6 +130,7 @@ Future<void> enterDocumentText(WidgetTester tester) async {
|
||||
LogicalKeyboardKey.keyT,
|
||||
],
|
||||
tester: tester,
|
||||
withKeyUp: true,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
+2
@@ -19,12 +19,14 @@ void main() {
|
||||
// Duplicate
|
||||
await tester.openMoreViewActions();
|
||||
await tester.duplicateByMoreViewActions();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(pageFinder, findsNWidgets(2));
|
||||
|
||||
// Delete
|
||||
await tester.openMoreViewActions();
|
||||
await tester.deleteByMoreViewActions();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(pageFinder, findsNWidgets(1));
|
||||
});
|
||||
|
||||
+6
-6
@@ -130,24 +130,24 @@ void main() {
|
||||
final searchEmojiTextField = find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is TextField &&
|
||||
widget.decoration!.hintText == LocaleKeys.emoji_search.tr(),
|
||||
widget.decoration!.hintText == LocaleKeys.search_label.tr(),
|
||||
);
|
||||
await tester.enterText(
|
||||
searchEmojiTextField,
|
||||
'hand',
|
||||
'punch',
|
||||
);
|
||||
|
||||
// change skin tone
|
||||
await tester.editor.changeEmojiSkinTone(EmojiSkinTone.dark);
|
||||
|
||||
// select an icon with skin tone
|
||||
const hand = '👋🏿';
|
||||
await tester.tapEmoji(hand);
|
||||
tester.expectToSeeDocumentIcon(hand);
|
||||
const punch = '👊🏿';
|
||||
await tester.tapEmoji(punch);
|
||||
tester.expectToSeeDocumentIcon(punch);
|
||||
tester.expectViewHasIcon(
|
||||
gettingStarted,
|
||||
ViewLayoutPB.Document,
|
||||
hand,
|
||||
punch,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
+12
-12
@@ -1,5 +1,5 @@
|
||||
import 'package:appflowy/workspace/application/settings/settings_dialog_bloc.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
@@ -8,8 +8,8 @@ import '../../shared/util.dart';
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('board add row test', () {
|
||||
testWidgets('Add card from header', (tester) async {
|
||||
group('notification test', () {
|
||||
testWidgets('enable notification', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
@@ -17,25 +17,25 @@ void main() {
|
||||
await tester.openSettingsPage(SettingsPage.notifications);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final switchFinder = find.byType(Switch);
|
||||
final toggleFinder = find.byType(Toggle).first;
|
||||
|
||||
// Defaults to enabled
|
||||
Switch switchWidget = tester.widget(switchFinder);
|
||||
expect(switchWidget.value, true);
|
||||
Toggle toggleWidget = tester.widget(toggleFinder);
|
||||
expect(toggleWidget.value, true);
|
||||
|
||||
// Disable
|
||||
await tester.tap(switchFinder);
|
||||
await tester.tap(toggleFinder);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
switchWidget = tester.widget(switchFinder);
|
||||
expect(switchWidget.value, false);
|
||||
toggleWidget = tester.widget(toggleFinder);
|
||||
expect(toggleWidget.value, false);
|
||||
|
||||
// Enable again
|
||||
await tester.tap(switchFinder);
|
||||
await tester.tap(toggleFinder);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
switchWidget = tester.widget(switchFinder);
|
||||
expect(switchWidget.value, true);
|
||||
toggleWidget = tester.widget(toggleFinder);
|
||||
expect(toggleWidget.value, true);
|
||||
});
|
||||
});
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../../shared/auth_operation.dart';
|
||||
import '../../shared/base.dart';
|
||||
import '../../shared/expectation.dart';
|
||||
import '../../shared/settings.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('Settings Billing', () {
|
||||
testWidgets('Local auth cannot see plan+billing', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapSignInAsGuest();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
await tester.openSettings();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// We check that another settings page is present to ensure
|
||||
// it's not a fluke
|
||||
expect(
|
||||
find.text(
|
||||
LocaleKeys.settings_workspacePage_menuLabel.tr(),
|
||||
skipOffstage: false,
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
expect(
|
||||
find.text(
|
||||
LocaleKeys.settings_planPage_menuLabel.tr(),
|
||||
skipOffstage: false,
|
||||
),
|
||||
findsNothing,
|
||||
);
|
||||
|
||||
expect(
|
||||
find.text(
|
||||
LocaleKeys.settings_billingPage_menuLabel.tr(),
|
||||
skipOffstage: false,
|
||||
),
|
||||
findsNothing,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'notifications_settings_test.dart' as notifications_settings_test;
|
||||
import 'settings_billing_test.dart' as settings_billing_test;
|
||||
import 'shortcuts_settings_test.dart' as shortcuts_settings_test;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
notifications_settings_test.main();
|
||||
settings_billing_test.main();
|
||||
shortcuts_settings_test.main();
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
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/workspace/application/settings/settings_dialog_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_shortcuts_view.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../../shared/keyboard.dart';
|
||||
import '../../shared/util.dart';
|
||||
import '../board/board_hide_groups_test.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('shortcuts test', () {
|
||||
testWidgets('can change and overwrite shortcut', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.openSettings();
|
||||
await tester.openSettingsPage(SettingsPage.shortcuts);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final backspaceCmd =
|
||||
LocaleKeys.settings_shortcutsPage_keybindings_backspace.tr();
|
||||
|
||||
// Input "Delete" into the search field
|
||||
await tester.enterText(find.byType(TextField), backspaceCmd);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.hoverOnWidget(
|
||||
find.descendant(
|
||||
of: find.byType(ShortcutSettingTile),
|
||||
matching: find.text(backspaceCmd),
|
||||
),
|
||||
onHover: () async {
|
||||
await tester.tap(find.byFlowySvg(FlowySvgs.edit_s));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await FlowyTestKeyboard.simulateKeyDownEvent(
|
||||
[
|
||||
LogicalKeyboardKey.delete,
|
||||
LogicalKeyboardKey.enter,
|
||||
],
|
||||
tester: tester,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
},
|
||||
);
|
||||
|
||||
// We expect to see conflict dialog
|
||||
expect(
|
||||
find.text(
|
||||
LocaleKeys.settings_shortcutsPage_conflictDialog_confirmLabel.tr(),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
// Press on confirm label
|
||||
await tester.tap(
|
||||
find.text(
|
||||
LocaleKeys.settings_shortcutsPage_conflictDialog_confirmLabel.tr(),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// We expect the first ShortcutSettingTile to have one
|
||||
// [KeyBadge] with `delete` label
|
||||
final first = tester.widget(find.byType(ShortcutSettingTile).first)
|
||||
as ShortcutSettingTile;
|
||||
expect(
|
||||
first.command.command,
|
||||
'delete',
|
||||
);
|
||||
|
||||
// And the second one which is `Delete left character` to have none
|
||||
// as it will have been overwritten
|
||||
final second = tester.widget(find.byType(ShortcutSettingTile).at(1))
|
||||
as ShortcutSettingTile;
|
||||
expect(
|
||||
second.command.command,
|
||||
'',
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_folder.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/shared/sidebar_folder.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
@@ -12,8 +12,8 @@ void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('sidebar expand test', () {
|
||||
bool isExpanded({required FolderCategoryType type}) {
|
||||
if (type == FolderCategoryType.private) {
|
||||
bool isExpanded({required FolderSpaceType type}) {
|
||||
if (type == FolderSpaceType.private) {
|
||||
return find
|
||||
.descendant(
|
||||
of: find.byType(PrivateSectionFolder),
|
||||
@@ -30,19 +30,19 @@ void main() {
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// first time is expanded
|
||||
expect(isExpanded(type: FolderCategoryType.private), true);
|
||||
expect(isExpanded(type: FolderSpaceType.private), true);
|
||||
|
||||
// collapse the personal folder
|
||||
await tester.tapButton(
|
||||
find.byTooltip(LocaleKeys.sideBar_clickToHidePrivate.tr()),
|
||||
);
|
||||
expect(isExpanded(type: FolderCategoryType.private), false);
|
||||
expect(isExpanded(type: FolderSpaceType.private), false);
|
||||
|
||||
// expand the personal folder
|
||||
await tester.tapButton(
|
||||
find.byTooltip(LocaleKeys.sideBar_clickToHidePrivate.tr()),
|
||||
);
|
||||
expect(isExpanded(type: FolderCategoryType.private), true);
|
||||
expect(isExpanded(type: FolderSpaceType.private), true);
|
||||
});
|
||||
});
|
||||
}
|
||||
+6
-6
@@ -1,5 +1,5 @@
|
||||
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/folder/_favorite_folder.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/favorites/favorite_folder.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
@@ -46,7 +46,7 @@ void main() {
|
||||
await tester.favoriteViewByName(names[1]);
|
||||
expect(
|
||||
tester.findFavoritePageName(names[1]),
|
||||
findsNWidgets(2),
|
||||
findsNWidgets(1),
|
||||
);
|
||||
|
||||
await tester.unfavoriteViewByName(gettingStarted);
|
||||
@@ -120,9 +120,9 @@ void main() {
|
||||
(widget) =>
|
||||
widget is SingleInnerViewItem &&
|
||||
widget.view.isFavorite &&
|
||||
widget.categoryType == FolderCategoryType.favorite,
|
||||
widget.spaceType == FolderSpaceType.favorite,
|
||||
),
|
||||
findsNWidgets(6),
|
||||
findsNWidgets(3),
|
||||
);
|
||||
|
||||
await tester.hoverOnPageName(
|
||||
@@ -135,7 +135,7 @@ void main() {
|
||||
|
||||
expect(
|
||||
tester.findAllFavoritePages(),
|
||||
findsNWidgets(3),
|
||||
findsNWidgets(2),
|
||||
);
|
||||
|
||||
await tester.hoverOnPageName(
|
||||
@@ -168,7 +168,7 @@ void main() {
|
||||
widget.isSelected != null &&
|
||||
widget.isSelected!(),
|
||||
),
|
||||
findsNWidgets(2),
|
||||
findsNWidgets(1),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -18,6 +18,9 @@ void main() {
|
||||
|
||||
// create document, board, grid and calendar views
|
||||
for (final value in ViewLayoutPB.values) {
|
||||
if (value == ViewLayoutPB.Chat) {
|
||||
continue;
|
||||
}
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: value.name,
|
||||
parentName: gettingStarted,
|
||||
@@ -46,6 +49,9 @@ void main() {
|
||||
|
||||
// create document, board, grid and calendar views
|
||||
for (final value in ViewLayoutPB.values) {
|
||||
if (value == ViewLayoutPB.Chat) {
|
||||
continue;
|
||||
}
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: value.name,
|
||||
parentName: gettingStarted,
|
||||
|
||||
@@ -39,6 +39,9 @@ void main() {
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
for (final layout in ViewLayoutPB.values) {
|
||||
if (layout == ViewLayoutPB.Chat) {
|
||||
continue;
|
||||
}
|
||||
// create a new page
|
||||
final name = 'AppFlowy_$layout';
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
@@ -66,6 +69,8 @@ void main() {
|
||||
case ViewLayoutPB.Calendar:
|
||||
expect(find.byType(CalendarPage), findsOneWidget);
|
||||
break;
|
||||
case ViewLayoutPB.Chat:
|
||||
break;
|
||||
}
|
||||
|
||||
await tester.openPage(gettingStarted);
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'sidebar_favorites_test.dart' as sidebar_favorite_test;
|
||||
import 'sidebar_icon_test.dart' as sidebar_icon_test;
|
||||
import 'sidebar_test.dart' as sidebar_test;
|
||||
|
||||
void startTesting() {
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// Sidebar integration tests
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/openai_client.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/ai_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';
|
||||
@@ -103,8 +103,8 @@ Future<AppFlowyEditor> setUpOpenAITesting(WidgetTester tester) async {
|
||||
}
|
||||
|
||||
Future<void> mockOpenAIRepository() async {
|
||||
await getIt.unregister<OpenAIRepository>();
|
||||
getIt.registerFactoryAsync<OpenAIRepository>(
|
||||
await getIt.unregister<AIRepository>();
|
||||
getIt.registerFactoryAsync<AIRepository>(
|
||||
() => Future.value(
|
||||
MockOpenAIRepository(),
|
||||
),
|
||||
|
||||
@@ -27,7 +27,7 @@ Future<void> runIntegration3OnDesktop() async {
|
||||
settings_test_runner.main();
|
||||
share_markdown_test.main();
|
||||
import_files_test.main();
|
||||
sidebar_test_runner.startTesting();
|
||||
board_test_runner.startTesting();
|
||||
sidebar_test_runner.main();
|
||||
board_test_runner.main();
|
||||
tabs_test.main();
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widget
|
||||
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/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_test/flutter_test.dart';
|
||||
|
||||
@@ -44,6 +45,12 @@ extension AppFlowyAuthTest on WidgetTester {
|
||||
assert(isSwitched == value);
|
||||
}
|
||||
|
||||
void assertToggleValue(Finder finder, bool value) {
|
||||
final Toggle switchWidget = widget(finder);
|
||||
final isSwitched = switchWidget.value;
|
||||
assert(isSwitched == value);
|
||||
}
|
||||
|
||||
void assertEnableEncryptSwitchValue(bool value) {
|
||||
assertSwitchValue(
|
||||
find.descendant(
|
||||
@@ -65,10 +72,10 @@ extension AppFlowyAuthTest on WidgetTester {
|
||||
}
|
||||
|
||||
void assertAppFlowyCloudEnableSyncSwitchValue(bool value) {
|
||||
assertSwitchValue(
|
||||
assertToggleValue(
|
||||
find.descendant(
|
||||
of: find.byType(AppFlowyCloudEnableSync),
|
||||
matching: find.byWidgetPredicate((widget) => widget is Switch),
|
||||
matching: find.byWidgetPredicate((widget) => widget is Toggle),
|
||||
),
|
||||
value,
|
||||
);
|
||||
@@ -86,7 +93,7 @@ extension AppFlowyAuthTest on WidgetTester {
|
||||
Future<void> toggleEnableSync(Type syncButton) async {
|
||||
final finder = find.descendant(
|
||||
of: find.byType(syncButton),
|
||||
matching: find.byWidgetPredicate((widget) => widget is Switch),
|
||||
matching: find.byWidgetPredicate((widget) => widget is Toggle),
|
||||
);
|
||||
|
||||
await tapButton(finder);
|
||||
|
||||
@@ -16,9 +16,9 @@ import 'package:appflowy/shared/feature_flags.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/presentation/screens/screens.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_new_page_button.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_workspace.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/shared/sidebar_new_page_button.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/view/draggable_view_item.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_add_button.dart';
|
||||
@@ -27,6 +27,7 @@ import 'package:appflowy/workspace/presentation/notifications/widgets/flowy_tab.
|
||||
import 'package:appflowy/workspace/presentation/notifications/widgets/notification_button.dart';
|
||||
import 'package:appflowy/workspace/presentation/notifications/widgets/notification_tab_bar.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/more_view_actions/more_view_actions.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/more_view_actions/widgets/common_view_action.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/view_title_bar.dart';
|
||||
@@ -60,6 +61,7 @@ extension CommonOperations on WidgetTester {
|
||||
/// Tap the + button on the home page.
|
||||
Future<void> tapAddViewButton({
|
||||
String name = gettingStarted,
|
||||
ViewLayoutPB layout = ViewLayoutPB.Document,
|
||||
}) async {
|
||||
await hoverOnPageName(
|
||||
name,
|
||||
@@ -279,7 +281,7 @@ extension CommonOperations on WidgetTester {
|
||||
bool openAfterCreated = true,
|
||||
}) async {
|
||||
// create a new page
|
||||
await tapAddViewButton(name: parentName ?? gettingStarted);
|
||||
await tapAddViewButton(name: parentName ?? gettingStarted, layout: layout);
|
||||
await tapButtonWithName(layout.menuName);
|
||||
final settingsOrFailure = await getIt<KeyValueStorage>().getWithFormat(
|
||||
KVKeys.showRenameDialogWhenCreatingNewFile,
|
||||
@@ -619,6 +621,18 @@ extension SettingsFinder on CommonFinders {
|
||||
matching: find.byType(Scrollable),
|
||||
)
|
||||
.first;
|
||||
|
||||
Finder findSettingsMenuScrollable() => find
|
||||
.descendant(
|
||||
of: find
|
||||
.descendant(
|
||||
of: find.byType(SettingsMenu),
|
||||
matching: find.byType(SingleChildScrollView),
|
||||
)
|
||||
.first,
|
||||
matching: find.byType(Scrollable),
|
||||
)
|
||||
.first;
|
||||
}
|
||||
|
||||
extension ViewLayoutPBTest on ViewLayoutPB {
|
||||
|
||||
@@ -130,12 +130,12 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
await openPage('v020', layout: ViewLayoutPB.Grid);
|
||||
}
|
||||
|
||||
Future<void> hoverOnFirstRowOfGrid() async {
|
||||
Future<void> hoverOnFirstRowOfGrid([Future<void> Function()? onHover]) async {
|
||||
final findRow = find.byType(GridRow);
|
||||
expect(findRow, findsWidgets);
|
||||
|
||||
final firstRow = findRow.first;
|
||||
await hoverOnWidget(firstRow);
|
||||
await hoverOnWidget(firstRow, onHover: onHover);
|
||||
}
|
||||
|
||||
Future<void> editCell({
|
||||
@@ -876,11 +876,13 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
}
|
||||
|
||||
Future<void> tapRowMenuButtonInGrid() async {
|
||||
expect(find.byType(RowMenuButton), findsOneWidget);
|
||||
await tapButton(find.byType(RowMenuButton));
|
||||
}
|
||||
|
||||
/// Should call [tapRowMenuButtonInGrid] first.
|
||||
Future<void> tapDeleteOnRowMenu() async {
|
||||
expect(find.text(LocaleKeys.grid_row_delete.tr()), findsOneWidget);
|
||||
await tapButtonWithName(LocaleKeys.grid_row_delete.tr());
|
||||
}
|
||||
|
||||
|
||||
@@ -81,15 +81,12 @@ class EditorOperations {
|
||||
|
||||
/// Taps the 'Remove Icon' button in the cover toolbar and the icon popover
|
||||
Future<void> tapRemoveIconButton({bool isInPicker = false}) async {
|
||||
Finder button =
|
||||
find.text(LocaleKeys.document_plugins_cover_removeIcon.tr());
|
||||
if (isInPicker) {
|
||||
button = find.descendant(
|
||||
of: find.byType(FlowyIconPicker),
|
||||
matching: button,
|
||||
);
|
||||
}
|
||||
|
||||
final Finder button = !isInPicker
|
||||
? find.text(LocaleKeys.document_plugins_cover_removeIcon.tr())
|
||||
: find.descendant(
|
||||
of: find.byType(FlowyIconPicker),
|
||||
matching: find.text(LocaleKeys.button_remove.tr()),
|
||||
);
|
||||
await tester.tapButton(button);
|
||||
}
|
||||
|
||||
|
||||
@@ -165,7 +165,7 @@ extension Expectation on WidgetTester {
|
||||
(widget) =>
|
||||
widget is SingleInnerViewItem &&
|
||||
widget.view.isFavorite &&
|
||||
widget.categoryType == FolderCategoryType.favorite &&
|
||||
widget.spaceType == FolderSpaceType.favorite &&
|
||||
widget.view.name == name &&
|
||||
widget.view.layout == layout,
|
||||
skipOffstage: false,
|
||||
@@ -175,7 +175,7 @@ extension Expectation on WidgetTester {
|
||||
(widget) =>
|
||||
widget is SingleInnerViewItem &&
|
||||
widget.view.isFavorite &&
|
||||
widget.categoryType == FolderCategoryType.favorite,
|
||||
widget.spaceType == FolderSpaceType.favorite,
|
||||
);
|
||||
|
||||
Finder findPageName(
|
||||
|
||||
@@ -5,10 +5,18 @@ class FlowyTestKeyboard {
|
||||
static Future<void> simulateKeyDownEvent(
|
||||
List<LogicalKeyboardKey> keys, {
|
||||
required flutter_test.WidgetTester tester,
|
||||
bool withKeyUp = false,
|
||||
}) async {
|
||||
for (final LogicalKeyboardKey key in keys) {
|
||||
await flutter_test.simulateKeyDownEvent(key);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
if (withKeyUp) {
|
||||
for (final LogicalKeyboardKey key in keys) {
|
||||
await flutter_test.simulateKeyUpEvent(key);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -1,7 +1,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/sidebar_setting.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/settings_workspace_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/settings_dialog.dart';
|
||||
@@ -32,6 +32,14 @@ extension AppFlowySettings on WidgetTester {
|
||||
final button = find.byWidgetPredicate(
|
||||
(widget) => widget is SettingsMenuElement && widget.page == page,
|
||||
);
|
||||
|
||||
await scrollUntilVisible(
|
||||
button,
|
||||
0,
|
||||
scrollable: find.findSettingsMenuScrollable(),
|
||||
);
|
||||
await pump();
|
||||
|
||||
expect(button, findsOneWidget);
|
||||
await tapButton(button);
|
||||
return;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/base/icon/icon_picker.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_workspace.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';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'base.dart';
|
||||
import 'util.dart';
|
||||
|
||||
extension AppFlowyWorkspace on WidgetTester {
|
||||
/// Open workspace menu
|
||||
@@ -36,12 +36,19 @@ extension AppFlowyWorkspace on WidgetTester {
|
||||
matching: find.byType(WorkspaceMoreActionList),
|
||||
);
|
||||
expect(moreButton, findsOneWidget);
|
||||
await tapButton(moreButton);
|
||||
await tapButton(find.findTextInFlowyText(LocaleKeys.button_rename.tr()));
|
||||
final input = find.byType(TextFormField);
|
||||
expect(input, findsOneWidget);
|
||||
await enterText(input, name);
|
||||
await tapButton(find.text(LocaleKeys.button_ok.tr()));
|
||||
await hoverOnWidget(
|
||||
moreButton,
|
||||
onHover: () async {
|
||||
await tapButton(moreButton);
|
||||
await tapButton(
|
||||
find.findTextInFlowyText(LocaleKeys.button_rename.tr()),
|
||||
);
|
||||
final input = find.byType(TextFormField);
|
||||
expect(input, findsOneWidget);
|
||||
await enterText(input, name);
|
||||
await tapButton(find.text(LocaleKeys.button_ok.tr()));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> changeWorkspaceIcon(String icon) async {
|
||||
|
||||
@@ -170,10 +170,10 @@ SPEC CHECKSUMS:
|
||||
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
|
||||
flowy_infra_ui: 0455e1fa8c51885aa1437848e361e99419f34ebc
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
|
||||
fluttertoast: fafc4fa4d01a6a9e4f772ecd190ffa525e9e2d9c
|
||||
image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb
|
||||
image_picker_ios: 99dfe1854b4fa34d0364e74a78448a0151025425
|
||||
integration_test: 13825b8a9334a850581300559b8839134b124670
|
||||
integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4
|
||||
irondash_engine_context: 3458bf979b90d616ffb8ae03a150bafe2e860cc9
|
||||
keyboard_height_plugin: 43fa8bba20fd5c4fdeed5076466b8b9d43cc6b86
|
||||
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
|
||||
@@ -191,4 +191,4 @@ SPEC CHECKSUMS:
|
||||
|
||||
PODFILE CHECKSUM: d0d9b4ff572d8695c38eb3f9b490f55cdfc57eca
|
||||
|
||||
COCOAPODS: 1.15.2
|
||||
COCOAPODS: 1.11.3
|
||||
@@ -66,5 +66,10 @@
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false />
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</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 =
|
||||
@@ -65,6 +72,11 @@ class KVKeys {
|
||||
/// {'feature_flag_1': true, 'feature_flag_2': false}
|
||||
static const String featureFlag = 'featureFlag';
|
||||
|
||||
/// The key for saving show notification icon option
|
||||
///
|
||||
/// The value is a boolean string
|
||||
static const String showNotificationIcon = 'showNotificationIcon';
|
||||
|
||||
/// The key for saving the last opened workspace id
|
||||
///
|
||||
/// The workspace id is a string.
|
||||
@@ -75,4 +87,25 @@ class KVKeys {
|
||||
///
|
||||
/// The value is a double string.
|
||||
static const String scaleFactor = 'scaleFactor';
|
||||
|
||||
/// The key for saving the last opened tab (favorite, recent, space etc.)
|
||||
///
|
||||
/// The value is a int string.
|
||||
static const String lastOpenedSpace = 'lastOpenedSpace';
|
||||
|
||||
/// The key for saving the space tab order
|
||||
///
|
||||
/// The value is a json string with the following format:
|
||||
/// [0, 1, 2]
|
||||
static const String spaceOrder = 'spaceOrder';
|
||||
|
||||
/// The key for saving the last opened space id (space A, space B)
|
||||
///
|
||||
/// The value is a string.
|
||||
static const String lastOpenedSpaceId = 'lastOpenedSpaceId';
|
||||
|
||||
/// The key for saving the upgrade space tag
|
||||
///
|
||||
/// The value is a boolean string
|
||||
static const String hasUpgradedSpace = 'hasUpgradedSpace060';
|
||||
}
|
||||
@@ -1,14 +1,16 @@
|
||||
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/icon_button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class CocoaWindowChannel {
|
||||
@@ -102,22 +104,40 @@ class MoveWindowDetectorState extends State<MoveWindowDetector> {
|
||||
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(
|
||||
children: [
|
||||
TextSpan(text: '${LocaleKeys.sideBar_closeSidebar.tr()}\n'),
|
||||
const TextSpan(text: 'Ctrl+\\'),
|
||||
],
|
||||
),
|
||||
child: FlowyIconButton(
|
||||
hoverColor: Colors.transparent,
|
||||
onPressed: () => context
|
||||
richMessage: textSpan,
|
||||
child: Listener(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onPointerDown: (_) => context
|
||||
.read<HomeSettingBloc>()
|
||||
.add(const HomeSettingEvent.collapseMenu()),
|
||||
iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4),
|
||||
icon: context.read<HomeSettingBloc>().state.isMenuCollapsed
|
||||
? const FlowySvg(FlowySvgs.show_menu_s)
|
||||
: const FlowySvg(FlowySvgs.hide_menu_m),
|
||||
child: FlowyHover(
|
||||
child: Container(
|
||||
width: 24,
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: const RotatedBox(
|
||||
quarterTurns: 2,
|
||||
child: FlowySvg(FlowySvgs.hide_menu_s),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:string_validator/string_validator.dart';
|
||||
import 'package:url_launcher/url_launcher.dart' as launcher;
|
||||
|
||||
|
||||
@@ -481,7 +481,7 @@ class _AFDropdownMenuState<T> extends State<AFDropdownMenu<T>> {
|
||||
|
||||
ButtonStyle effectiveStyle = entry.style ?? defaultStyle;
|
||||
final Color focusedBackgroundColor = effectiveStyle.foregroundColor
|
||||
?.resolve(<MaterialState>{MaterialState.focused}) ??
|
||||
?.resolve(<WidgetState>{WidgetState.focused}) ??
|
||||
Theme.of(context).colorScheme.onSurface;
|
||||
|
||||
Widget label = entry.labelWidget ?? Text(entry.label);
|
||||
@@ -499,7 +499,7 @@ class _AFDropdownMenuState<T> extends State<AFDropdownMenu<T>> {
|
||||
// color will also change to foregroundColor.withOpacity(0.12).
|
||||
effectiveStyle = entry.enabled && i == focusedIndex
|
||||
? effectiveStyle.copyWith(
|
||||
backgroundColor: MaterialStatePropertyAll<Color>(
|
||||
backgroundColor: WidgetStatePropertyAll<Color>(
|
||||
focusedBackgroundColor.withOpacity(0.12),
|
||||
),
|
||||
)
|
||||
@@ -628,17 +628,17 @@ class _AFDropdownMenuState<T> extends State<AFDropdownMenu<T>> {
|
||||
final double? anchorWidth = getWidth(_anchorKey);
|
||||
if (widget.width != null) {
|
||||
effectiveMenuStyle = effectiveMenuStyle.copyWith(
|
||||
minimumSize: MaterialStatePropertyAll<Size?>(Size(widget.width!, 0.0)),
|
||||
minimumSize: WidgetStatePropertyAll<Size?>(Size(widget.width!, 0.0)),
|
||||
);
|
||||
} else if (anchorWidth != null) {
|
||||
effectiveMenuStyle = effectiveMenuStyle.copyWith(
|
||||
minimumSize: MaterialStatePropertyAll<Size?>(Size(anchorWidth, 0.0)),
|
||||
minimumSize: WidgetStatePropertyAll<Size?>(Size(anchorWidth, 0.0)),
|
||||
);
|
||||
}
|
||||
|
||||
if (widget.menuHeight != null) {
|
||||
effectiveMenuStyle = effectiveMenuStyle.copyWith(
|
||||
maximumSize: MaterialStatePropertyAll<Size>(
|
||||
maximumSize: WidgetStatePropertyAll<Size>(
|
||||
Size(double.infinity, widget.menuHeight!),
|
||||
),
|
||||
);
|
||||
@@ -1029,8 +1029,8 @@ class _DropdownMenuDefaultsM3 extends DropdownMenuThemeData {
|
||||
@override
|
||||
MenuStyle get menuStyle {
|
||||
return const MenuStyle(
|
||||
minimumSize: MaterialStatePropertyAll<Size>(Size(_kMinimumWidth, 0.0)),
|
||||
maximumSize: MaterialStatePropertyAll<Size>(Size.infinite),
|
||||
minimumSize: WidgetStatePropertyAll<Size>(Size(_kMinimumWidth, 0.0)),
|
||||
maximumSize: WidgetStatePropertyAll<Size>(Size.infinite),
|
||||
visualDensity: VisualDensity.standard,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
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';
|
||||
|
||||
@@ -14,15 +16,15 @@ import 'package:go_router/go_router.dart';
|
||||
|
||||
extension MobileRouter on BuildContext {
|
||||
Future<void> pushView(ViewPB view, [Map<String, dynamic>? arguments]) async {
|
||||
await push(
|
||||
Uri(
|
||||
path: view.routeName,
|
||||
queryParameters: view.queryParameters(arguments),
|
||||
).toString(),
|
||||
).then((value) {
|
||||
getIt<MenuSharedState>().latestOpenView = view;
|
||||
getIt<CachedRecentService>().updateRecentViews([view.id], true);
|
||||
});
|
||||
// set the current view before pushing the new view
|
||||
getIt<MenuSharedState>().latestOpenView = view;
|
||||
unawaited(getIt<CachedRecentService>().updateRecentViews([view.id], true));
|
||||
|
||||
final uri = Uri(
|
||||
path: view.routeName,
|
||||
queryParameters: view.queryParameters(arguments),
|
||||
).toString();
|
||||
await push(uri);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +39,9 @@ extension on ViewPB {
|
||||
return MobileCalendarScreen.routeName;
|
||||
case ViewLayoutPB.Board:
|
||||
return MobileBoardScreen.routeName;
|
||||
case ViewLayoutPB.Chat:
|
||||
return MobileChatScreen.routeName;
|
||||
|
||||
default:
|
||||
throw UnimplementedError('routeName for $this is not implemented');
|
||||
}
|
||||
@@ -65,6 +70,11 @@ extension on ViewPB {
|
||||
MobileBoardScreen.viewId: id,
|
||||
MobileBoardScreen.viewTitle: name,
|
||||
};
|
||||
case ViewLayoutPB.Chat:
|
||||
return {
|
||||
MobileChatScreen.viewId: id,
|
||||
MobileChatScreen.viewTitle: name,
|
||||
};
|
||||
default:
|
||||
throw UnimplementedError(
|
||||
'queryParameters for $this is not implemented',
|
||||
|
||||
@@ -57,7 +57,19 @@ class RecentViewBloc extends Bloc<RecentViewEvent, RecentViewState> {
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// only document supports the cover
|
||||
if (view.layout != ViewLayoutPB.Document) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
name: view.name,
|
||||
icon: view.icon.value,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final cover = getCoverV2();
|
||||
|
||||
if (cover != null) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
|
||||
@@ -6,8 +6,8 @@ import 'package:appflowy/mobile/presentation/base/view_page/app_bar_buttons.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/plugins/shared/sync_indicator.dart';
|
||||
import 'package:appflowy/shared/feature_flags.dart';
|
||||
import 'package:appflowy/startup/plugin/plugin.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
||||
@@ -154,7 +154,10 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
||||
(view) {
|
||||
final plugin = view.plugin(arguments: widget.arguments ?? const {})
|
||||
..init();
|
||||
return plugin.widgetBuilder.buildWidget(shrinkWrap: false);
|
||||
return plugin.widgetBuilder.buildWidget(
|
||||
shrinkWrap: false,
|
||||
context: PluginContext(userProfile: state.userProfilePB),
|
||||
);
|
||||
},
|
||||
(error) {
|
||||
return FlowyMobileStateContainer.error(
|
||||
@@ -191,13 +194,6 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
view: view,
|
||||
),
|
||||
const HSpace(16.0),
|
||||
DocumentSyncIndicator(view: view),
|
||||
const HSpace(12.0),
|
||||
]);
|
||||
} else {
|
||||
actions.addAll([
|
||||
DatabaseSyncIndicator(view: view),
|
||||
const HSpace(12.0),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart' hide WidgetBuilder;
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TypeOptionMenuItemValue<T> {
|
||||
|
||||
+3
-2
@@ -12,6 +12,7 @@ import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/prelude.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -92,7 +93,7 @@ class MobileViewPageMoreButton extends StatelessWidget {
|
||||
context,
|
||||
showDragHandle: true,
|
||||
showDivider: false,
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
backgroundColor: AFThemeExtension.of(context).background,
|
||||
builder: (_) => MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider.value(value: context.read<ViewBloc>()),
|
||||
@@ -144,7 +145,7 @@ class MobileViewPageLayoutButton extends StatelessWidget {
|
||||
showDoneButton: true,
|
||||
showHeader: true,
|
||||
title: LocaleKeys.pageStyle_title.tr(),
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
backgroundColor: AFThemeExtension.of(context).background,
|
||||
builder: (_) => MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider.value(value: context.read<DocumentPageStyleBloc>()),
|
||||
|
||||
+1
@@ -38,6 +38,7 @@ class MobileViewPageMoreBottomSheet extends StatelessWidget {
|
||||
case MobileViewBottomSheetBodyAction.removeFromFavorites:
|
||||
context.pop();
|
||||
context.read<FavoriteBloc>().add(FavoriteEvent.toggle(view));
|
||||
|
||||
break;
|
||||
case MobileViewBottomSheetBodyAction.undo:
|
||||
EditorNotification.undo().post();
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -19,7 +20,7 @@ class BottomSheetActionWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final iconColor =
|
||||
this.iconColor ?? Theme.of(context).colorScheme.onBackground;
|
||||
this.iconColor ?? AFThemeExtension.of(context).onBackground;
|
||||
|
||||
if (svg == null) {
|
||||
return OutlinedButton(
|
||||
|
||||
+22
-8
@@ -21,40 +21,54 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget {
|
||||
children: [
|
||||
FlowyOptionTile.text(
|
||||
text: LocaleKeys.document_menuName.tr(),
|
||||
height: 52.0,
|
||||
leftIcon: const FlowySvg(
|
||||
FlowySvgs.document_s,
|
||||
size: Size.square(20),
|
||||
FlowySvgs.icon_document_s,
|
||||
size: Size.square(18),
|
||||
),
|
||||
showTopBorder: false,
|
||||
onTap: () => onAction(ViewLayoutPB.Document),
|
||||
),
|
||||
FlowyOptionTile.text(
|
||||
text: LocaleKeys.grid_menuName.tr(),
|
||||
height: 52.0,
|
||||
leftIcon: const FlowySvg(
|
||||
FlowySvgs.grid_s,
|
||||
size: Size.square(20),
|
||||
FlowySvgs.icon_grid_s,
|
||||
size: Size.square(18),
|
||||
),
|
||||
showTopBorder: false,
|
||||
onTap: () => onAction(ViewLayoutPB.Grid),
|
||||
),
|
||||
FlowyOptionTile.text(
|
||||
text: LocaleKeys.board_menuName.tr(),
|
||||
height: 52.0,
|
||||
leftIcon: const FlowySvg(
|
||||
FlowySvgs.board_s,
|
||||
size: Size.square(20),
|
||||
FlowySvgs.icon_board_s,
|
||||
size: Size.square(18),
|
||||
),
|
||||
showTopBorder: false,
|
||||
onTap: () => onAction(ViewLayoutPB.Board),
|
||||
),
|
||||
FlowyOptionTile.text(
|
||||
text: LocaleKeys.calendar_menuName.tr(),
|
||||
height: 52.0,
|
||||
leftIcon: const FlowySvg(
|
||||
FlowySvgs.date_s,
|
||||
size: Size.square(20),
|
||||
FlowySvgs.icon_calendar_s,
|
||||
size: Size.square(18),
|
||||
),
|
||||
showTopBorder: false,
|
||||
onTap: () => onAction(ViewLayoutPB.Calendar),
|
||||
),
|
||||
FlowyOptionTile.text(
|
||||
text: LocaleKeys.chat_newChat.tr(),
|
||||
height: 52.0,
|
||||
leftIcon: const FlowySvg(
|
||||
FlowySvgs.chat_ai_page_s,
|
||||
size: Size.square(18),
|
||||
),
|
||||
showTopBorder: false,
|
||||
onTap: () => onAction(ViewLayoutPB.Chat),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
+2
-1
@@ -33,6 +33,7 @@ class BlockActionBottomSheet extends StatelessWidget {
|
||||
FlowySvgs.arrow_up_s,
|
||||
size: Size.square(20),
|
||||
),
|
||||
showTopBorder: false,
|
||||
onTap: () => onAction(BlockActionBottomSheetType.insertAbove),
|
||||
),
|
||||
FlowyOptionTile.text(
|
||||
@@ -48,7 +49,7 @@ class BlockActionBottomSheet extends StatelessWidget {
|
||||
FlowyOptionTile.text(
|
||||
showTopBorder: false,
|
||||
text: LocaleKeys.button_duplicate.tr(),
|
||||
leftIcon: const FlowySvg(FlowySvgs.m_field_copy_s),
|
||||
leftIcon: const FlowySvg(FlowySvgs.m_duplicate_s),
|
||||
onTap: () => onAction(BlockActionBottomSheetType.duplicate),
|
||||
),
|
||||
|
||||
|
||||
+88
-1
@@ -1,9 +1,17 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/show_flowy_mobile_confirm_dialog.dart';
|
||||
import 'package:appflowy/startup/tasks/app_widget.dart';
|
||||
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/recent/recent_views_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
|
||||
enum MobileBottomSheetType {
|
||||
view,
|
||||
@@ -14,11 +22,13 @@ class MobileViewItemBottomSheet extends StatefulWidget {
|
||||
const MobileViewItemBottomSheet({
|
||||
super.key,
|
||||
required this.view,
|
||||
required this.actions,
|
||||
this.defaultType = MobileBottomSheetType.view,
|
||||
});
|
||||
|
||||
final ViewPB view;
|
||||
final MobileBottomSheetType defaultType;
|
||||
final List<MobileViewItemBottomSheetBodyAction> actions;
|
||||
|
||||
@override
|
||||
State<MobileViewItemBottomSheet> createState() =>
|
||||
@@ -27,12 +37,14 @@ class MobileViewItemBottomSheet extends StatefulWidget {
|
||||
|
||||
class _MobileViewItemBottomSheetState extends State<MobileViewItemBottomSheet> {
|
||||
MobileBottomSheetType type = MobileBottomSheetType.view;
|
||||
final fToast = FToast();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
type = widget.defaultType;
|
||||
fToast.init(AppGlobals.context);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -40,6 +52,7 @@ class _MobileViewItemBottomSheetState extends State<MobileViewItemBottomSheet> {
|
||||
switch (type) {
|
||||
case MobileBottomSheetType.view:
|
||||
return MobileViewItemBottomSheetBody(
|
||||
actions: widget.actions,
|
||||
isFavorite: widget.view.isFavorite,
|
||||
onAction: (action) {
|
||||
switch (action) {
|
||||
@@ -59,7 +72,6 @@ class _MobileViewItemBottomSheetState extends State<MobileViewItemBottomSheet> {
|
||||
case MobileViewItemBottomSheetBodyAction.delete:
|
||||
Navigator.pop(context);
|
||||
context.read<ViewBloc>().add(const ViewEvent.delete());
|
||||
|
||||
break;
|
||||
case MobileViewItemBottomSheetBodyAction.addToFavorites:
|
||||
case MobileViewItemBottomSheetBodyAction.removeFromFavorites:
|
||||
@@ -68,6 +80,11 @@ class _MobileViewItemBottomSheetState extends State<MobileViewItemBottomSheet> {
|
||||
.read<FavoriteBloc>()
|
||||
.add(FavoriteEvent.toggle(widget.view));
|
||||
break;
|
||||
case MobileViewItemBottomSheetBodyAction.removeFromRecent:
|
||||
_removeFromRecent(context);
|
||||
break;
|
||||
case MobileViewItemBottomSheetBodyAction.divider:
|
||||
break;
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -83,4 +100,74 @@ class _MobileViewItemBottomSheetState extends State<MobileViewItemBottomSheet> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _removeFromRecent(BuildContext context) async {
|
||||
final viewId = context.read<ViewBloc>().view.id;
|
||||
final recentViewsBloc = context.read<RecentViewsBloc>();
|
||||
Navigator.pop(context);
|
||||
|
||||
await _showConfirmDialog(
|
||||
onDelete: () {
|
||||
recentViewsBloc.add(RecentViewsEvent.removeRecentViews([viewId]));
|
||||
|
||||
fToast.showToast(
|
||||
child: const _RemoveToast(),
|
||||
positionedToastBuilder: (context, child) {
|
||||
return Positioned.fill(
|
||||
top: 450,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showConfirmDialog({required VoidCallback onDelete}) async {
|
||||
await showFlowyCupertinoConfirmDialog(
|
||||
title: LocaleKeys.sideBar_removePageFromRecent.tr(),
|
||||
leftButton: FlowyText.regular(
|
||||
LocaleKeys.button_cancel.tr(),
|
||||
color: const Color(0xFF1456F0),
|
||||
),
|
||||
rightButton: FlowyText.medium(
|
||||
LocaleKeys.button_delete.tr(),
|
||||
color: const Color(0xFFFE0220),
|
||||
),
|
||||
onRightButtonPressed: (context) {
|
||||
onDelete();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RemoveToast extends StatelessWidget {
|
||||
const _RemoveToast();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 13.0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
color: const Color(0xE5171717),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const FlowySvg(
|
||||
FlowySvgs.success_s,
|
||||
blendMode: null,
|
||||
),
|
||||
const HSpace(8.0),
|
||||
FlowyText.regular(
|
||||
LocaleKeys.sideBar_removeSuccess.tr(),
|
||||
fontSize: 16.0,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+121
-48
@@ -1,6 +1,6 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -11,6 +11,8 @@ enum MobileViewItemBottomSheetBodyAction {
|
||||
delete,
|
||||
addToFavorites,
|
||||
removeFromFavorites,
|
||||
divider,
|
||||
removeFromRecent,
|
||||
}
|
||||
|
||||
class MobileViewItemBottomSheetBody extends StatelessWidget {
|
||||
@@ -18,63 +20,134 @@ class MobileViewItemBottomSheetBody extends StatelessWidget {
|
||||
super.key,
|
||||
this.isFavorite = false,
|
||||
required this.onAction,
|
||||
required this.actions,
|
||||
});
|
||||
|
||||
final bool isFavorite;
|
||||
final void Function(MobileViewItemBottomSheetBodyAction action) onAction;
|
||||
final List<MobileViewItemBottomSheetBodyAction> actions;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.button_rename.tr(),
|
||||
icon: FlowySvgs.m_rename_s,
|
||||
onTap: () => onAction(
|
||||
MobileViewItemBottomSheetBodyAction.rename,
|
||||
),
|
||||
),
|
||||
_divider(),
|
||||
MobileQuickActionButton(
|
||||
text: isFavorite
|
||||
? LocaleKeys.button_removeFromFavorites.tr()
|
||||
: LocaleKeys.button_addToFavorites.tr(),
|
||||
icon: isFavorite
|
||||
? FlowySvgs.m_favorite_selected_lg
|
||||
: FlowySvgs.m_favorite_unselected_lg,
|
||||
iconColor: isFavorite ? Colors.yellow : null,
|
||||
onTap: () => onAction(
|
||||
isFavorite
|
||||
? MobileViewItemBottomSheetBodyAction.removeFromFavorites
|
||||
: MobileViewItemBottomSheetBodyAction.addToFavorites,
|
||||
),
|
||||
),
|
||||
_divider(),
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.button_duplicate.tr(),
|
||||
icon: FlowySvgs.m_duplicate_s,
|
||||
onTap: () => onAction(
|
||||
MobileViewItemBottomSheetBodyAction.duplicate,
|
||||
),
|
||||
),
|
||||
_divider(),
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.button_delete.tr(),
|
||||
textColor: Theme.of(context).colorScheme.error,
|
||||
icon: FlowySvgs.m_delete_s,
|
||||
iconColor: Theme.of(context).colorScheme.error,
|
||||
onTap: () => onAction(
|
||||
MobileViewItemBottomSheetBodyAction.delete,
|
||||
),
|
||||
),
|
||||
_divider(),
|
||||
],
|
||||
children:
|
||||
actions.map((action) => _buildActionButton(context, action)).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _divider() => const Divider(
|
||||
height: 8.5,
|
||||
thickness: 0.5,
|
||||
);
|
||||
Widget _buildActionButton(
|
||||
BuildContext context,
|
||||
MobileViewItemBottomSheetBodyAction action,
|
||||
) {
|
||||
switch (action) {
|
||||
case MobileViewItemBottomSheetBodyAction.rename:
|
||||
return FlowyOptionTile.text(
|
||||
text: LocaleKeys.button_rename.tr(),
|
||||
height: 52.0,
|
||||
leftIcon: const FlowySvg(
|
||||
FlowySvgs.view_item_rename_s,
|
||||
size: Size.square(18),
|
||||
),
|
||||
showTopBorder: false,
|
||||
showBottomBorder: false,
|
||||
onTap: () => onAction(
|
||||
MobileViewItemBottomSheetBodyAction.rename,
|
||||
),
|
||||
);
|
||||
case MobileViewItemBottomSheetBodyAction.duplicate:
|
||||
return FlowyOptionTile.text(
|
||||
text: LocaleKeys.button_duplicate.tr(),
|
||||
height: 52.0,
|
||||
leftIcon: const FlowySvg(
|
||||
FlowySvgs.duplicate_s,
|
||||
size: Size.square(18),
|
||||
),
|
||||
showTopBorder: false,
|
||||
showBottomBorder: false,
|
||||
onTap: () => onAction(
|
||||
MobileViewItemBottomSheetBodyAction.duplicate,
|
||||
),
|
||||
);
|
||||
|
||||
case MobileViewItemBottomSheetBodyAction.share:
|
||||
return FlowyOptionTile.text(
|
||||
text: LocaleKeys.button_share.tr(),
|
||||
height: 52.0,
|
||||
leftIcon: const FlowySvg(
|
||||
FlowySvgs.share_s,
|
||||
size: Size.square(18),
|
||||
),
|
||||
showTopBorder: false,
|
||||
showBottomBorder: false,
|
||||
onTap: () => onAction(
|
||||
MobileViewItemBottomSheetBodyAction.share,
|
||||
),
|
||||
);
|
||||
case MobileViewItemBottomSheetBodyAction.delete:
|
||||
return FlowyOptionTile.text(
|
||||
text: LocaleKeys.button_delete.tr(),
|
||||
height: 52.0,
|
||||
textColor: Theme.of(context).colorScheme.error,
|
||||
leftIcon: FlowySvg(
|
||||
FlowySvgs.trash_s,
|
||||
size: const Size.square(18),
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
showTopBorder: false,
|
||||
showBottomBorder: false,
|
||||
onTap: () => onAction(
|
||||
MobileViewItemBottomSheetBodyAction.delete,
|
||||
),
|
||||
);
|
||||
case MobileViewItemBottomSheetBodyAction.addToFavorites:
|
||||
return FlowyOptionTile.text(
|
||||
height: 52.0,
|
||||
text: LocaleKeys.button_addToFavorites.tr(),
|
||||
leftIcon: const FlowySvg(
|
||||
FlowySvgs.favorite_s,
|
||||
size: Size.square(18),
|
||||
),
|
||||
showTopBorder: false,
|
||||
showBottomBorder: false,
|
||||
onTap: () => onAction(
|
||||
MobileViewItemBottomSheetBodyAction.addToFavorites,
|
||||
),
|
||||
);
|
||||
case MobileViewItemBottomSheetBodyAction.removeFromFavorites:
|
||||
return FlowyOptionTile.text(
|
||||
height: 52.0,
|
||||
text: LocaleKeys.button_removeFromFavorites.tr(),
|
||||
leftIcon: const FlowySvg(
|
||||
FlowySvgs.favorite_section_remove_from_favorite_s,
|
||||
size: Size.square(18),
|
||||
),
|
||||
showTopBorder: false,
|
||||
showBottomBorder: false,
|
||||
onTap: () => onAction(
|
||||
MobileViewItemBottomSheetBodyAction.removeFromFavorites,
|
||||
),
|
||||
);
|
||||
case MobileViewItemBottomSheetBodyAction.removeFromRecent:
|
||||
return FlowyOptionTile.text(
|
||||
height: 52.0,
|
||||
text: LocaleKeys.button_removeFromRecent.tr(),
|
||||
leftIcon: const FlowySvg(
|
||||
FlowySvgs.remove_from_recent_s,
|
||||
size: Size.square(18),
|
||||
),
|
||||
showTopBorder: false,
|
||||
showBottomBorder: false,
|
||||
onTap: () => onAction(
|
||||
MobileViewItemBottomSheetBodyAction.removeFromRecent,
|
||||
),
|
||||
);
|
||||
|
||||
case MobileViewItemBottomSheetBodyAction.divider:
|
||||
return const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Divider(height: 0.5),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-7
@@ -89,7 +89,7 @@ class MobileViewBottomSheetBody extends StatelessWidget {
|
||||
children: [
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.button_rename.tr(),
|
||||
icon: FlowySvgs.m_rename_s,
|
||||
icon: FlowySvgs.view_item_rename_s,
|
||||
onTap: () => onAction(
|
||||
MobileViewBottomSheetBodyAction.rename,
|
||||
),
|
||||
@@ -99,10 +99,7 @@ class MobileViewBottomSheetBody extends StatelessWidget {
|
||||
text: isFavorite
|
||||
? LocaleKeys.button_removeFromFavorites.tr()
|
||||
: LocaleKeys.button_addToFavorites.tr(),
|
||||
icon: isFavorite
|
||||
? FlowySvgs.m_favorite_selected_lg
|
||||
: FlowySvgs.m_favorite_unselected_lg,
|
||||
iconColor: isFavorite ? Colors.yellow : null,
|
||||
icon: isFavorite ? FlowySvgs.unfavorite_s : FlowySvgs.favorite_s,
|
||||
onTap: () => onAction(
|
||||
isFavorite
|
||||
? MobileViewBottomSheetBodyAction.removeFromFavorites
|
||||
@@ -112,7 +109,7 @@ class MobileViewBottomSheetBody extends StatelessWidget {
|
||||
_divider(),
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.button_duplicate.tr(),
|
||||
icon: FlowySvgs.m_duplicate_s,
|
||||
icon: FlowySvgs.duplicate_s,
|
||||
onTap: () => onAction(
|
||||
MobileViewBottomSheetBodyAction.duplicate,
|
||||
),
|
||||
@@ -121,7 +118,7 @@ class MobileViewBottomSheetBody extends StatelessWidget {
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.button_delete.tr(),
|
||||
textColor: Theme.of(context).colorScheme.error,
|
||||
icon: FlowySvgs.m_delete_s,
|
||||
icon: FlowySvgs.trash_s,
|
||||
iconColor: Theme.of(context).colorScheme.error,
|
||||
onTap: () => onAction(
|
||||
MobileViewBottomSheetBodyAction.delete,
|
||||
|
||||
+124
-19
@@ -1,8 +1,15 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/shared/mobile_page_card.dart';
|
||||
import 'package:appflowy/mobile/presentation/page_item/mobile_slide_action_button.dart';
|
||||
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/recent/recent_views_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||
@@ -11,11 +18,14 @@ enum MobilePaneActionType {
|
||||
delete,
|
||||
addToFavorites,
|
||||
removeFromFavorites,
|
||||
more;
|
||||
more,
|
||||
add;
|
||||
|
||||
MobileSlideActionButton actionButton(
|
||||
BuildContext context,
|
||||
) {
|
||||
BuildContext context, {
|
||||
MobilePageCardType? cardType,
|
||||
FolderSpaceType? spaceType,
|
||||
}) {
|
||||
switch (this) {
|
||||
case MobilePaneActionType.delete:
|
||||
return MobileSlideActionButton(
|
||||
@@ -27,45 +37,89 @@ enum MobilePaneActionType {
|
||||
);
|
||||
case MobilePaneActionType.removeFromFavorites:
|
||||
return MobileSlideActionButton(
|
||||
backgroundColor: Colors.orange,
|
||||
svg: FlowySvgs.favorite_s,
|
||||
backgroundColor: const Color(0xFFFA217F),
|
||||
svg: FlowySvgs.favorite_section_remove_from_favorite_s,
|
||||
size: 24.0,
|
||||
onPressed: (context) => context
|
||||
.read<FavoriteBloc>()
|
||||
.add(FavoriteEvent.toggle(context.read<ViewBloc>().view)),
|
||||
);
|
||||
case MobilePaneActionType.addToFavorites:
|
||||
return MobileSlideActionButton(
|
||||
backgroundColor: Colors.orange,
|
||||
svg: FlowySvgs.m_favorite_unselected_lg,
|
||||
size: 34.0,
|
||||
backgroundColor: const Color(0xFF00C8FF),
|
||||
svg: FlowySvgs.favorite_s,
|
||||
size: 24.0,
|
||||
onPressed: (context) => context
|
||||
.read<FavoriteBloc>()
|
||||
.add(FavoriteEvent.toggle(context.read<ViewBloc>().view)),
|
||||
);
|
||||
case MobilePaneActionType.more:
|
||||
case MobilePaneActionType.add:
|
||||
return MobileSlideActionButton(
|
||||
backgroundColor: Colors.grey,
|
||||
svg: FlowySvgs.three_dots_vertical_s,
|
||||
backgroundColor: const Color(0xFF00C8FF),
|
||||
svg: FlowySvgs.add_m,
|
||||
size: 28.0,
|
||||
onPressed: (context) {
|
||||
final viewBloc = context.read<ViewBloc>();
|
||||
final view = viewBloc.state.view;
|
||||
final title = view.name;
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
showHeader: true,
|
||||
title: title,
|
||||
showDragHandle: true,
|
||||
showCloseButton: true,
|
||||
useRootNavigator: true,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
builder: (sheetContext) {
|
||||
return AddNewPageWidgetBottomSheet(
|
||||
view: view,
|
||||
onAction: (layout) {
|
||||
Navigator.of(sheetContext).pop();
|
||||
viewBloc.add(
|
||||
ViewEvent.createView(
|
||||
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||
layout,
|
||||
section: spaceType!.toViewSectionPB,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
case MobilePaneActionType.more:
|
||||
return MobileSlideActionButton(
|
||||
backgroundColor: const Color(0xE5515563),
|
||||
svg: FlowySvgs.three_dots_s,
|
||||
size: 24.0,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(10),
|
||||
bottomLeft: Radius.circular(10),
|
||||
),
|
||||
onPressed: (context) {
|
||||
final viewBloc = context.read<ViewBloc>();
|
||||
final favoriteBloc = context.read<FavoriteBloc>();
|
||||
final recentViewsBloc = context.read<RecentViewsBloc?>();
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
showDragHandle: true,
|
||||
showDivider: false,
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
useRootNavigator: true,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
builder: (context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider.value(value: viewBloc),
|
||||
BlocProvider.value(value: favoriteBloc),
|
||||
if (recentViewsBloc != null)
|
||||
BlocProvider.value(value: recentViewsBloc),
|
||||
],
|
||||
child: BlocBuilder<ViewBloc, ViewState>(
|
||||
builder: (context, state) {
|
||||
return MobileViewItemBottomSheet(
|
||||
view: viewBloc.state.view,
|
||||
actions: _buildActions(state.view, cardType: cardType),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -76,19 +130,70 @@ enum MobilePaneActionType {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
List<MobileViewItemBottomSheetBodyAction> _buildActions(
|
||||
ViewPB view, {
|
||||
MobilePageCardType? cardType,
|
||||
}) {
|
||||
final isFavorite = view.isFavorite;
|
||||
|
||||
if (cardType != null) {
|
||||
switch (cardType) {
|
||||
case MobilePageCardType.recent:
|
||||
return [
|
||||
isFavorite
|
||||
? MobileViewItemBottomSheetBodyAction.removeFromFavorites
|
||||
: MobileViewItemBottomSheetBodyAction.addToFavorites,
|
||||
MobileViewItemBottomSheetBodyAction.divider,
|
||||
if (view.layout != ViewLayoutPB.Chat)
|
||||
MobileViewItemBottomSheetBodyAction.duplicate,
|
||||
MobileViewItemBottomSheetBodyAction.divider,
|
||||
MobileViewItemBottomSheetBodyAction.removeFromRecent,
|
||||
];
|
||||
case MobilePageCardType.favorite:
|
||||
return [
|
||||
isFavorite
|
||||
? MobileViewItemBottomSheetBodyAction.removeFromFavorites
|
||||
: MobileViewItemBottomSheetBodyAction.addToFavorites,
|
||||
MobileViewItemBottomSheetBodyAction.divider,
|
||||
MobileViewItemBottomSheetBodyAction.duplicate,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
isFavorite
|
||||
? MobileViewItemBottomSheetBodyAction.removeFromFavorites
|
||||
: MobileViewItemBottomSheetBodyAction.addToFavorites,
|
||||
MobileViewItemBottomSheetBodyAction.divider,
|
||||
MobileViewItemBottomSheetBodyAction.rename,
|
||||
if (view.layout != ViewLayoutPB.Chat)
|
||||
MobileViewItemBottomSheetBodyAction.duplicate,
|
||||
MobileViewItemBottomSheetBodyAction.divider,
|
||||
MobileViewItemBottomSheetBodyAction.delete,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
ActionPane buildEndActionPane(
|
||||
BuildContext context,
|
||||
List<MobilePaneActionType> actions,
|
||||
) {
|
||||
List<MobilePaneActionType> actions, {
|
||||
bool needSpace = true,
|
||||
MobilePageCardType? cardType,
|
||||
FolderSpaceType? spaceType,
|
||||
}) {
|
||||
return ActionPane(
|
||||
motion: const ScrollMotion(),
|
||||
extentRatio: actions.length / 5,
|
||||
children: actions
|
||||
.map(
|
||||
(action) => action.actionButton(context),
|
||||
)
|
||||
.toList(),
|
||||
children: [
|
||||
if (needSpace) const HSpace(20),
|
||||
...actions.map(
|
||||
(action) => action.actionButton(
|
||||
context,
|
||||
spaceType: spaceType,
|
||||
cardType: cardType,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_buttons.dart';
|
||||
import 'package:appflowy/plugins/base/drag_handler.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart' hide WidgetBuilder;
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
extension BottomSheetPaddingExtension on BuildContext {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import 'package:appflowy/mobile/presentation/base/mobile_view_page.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MobileChatScreen extends StatelessWidget {
|
||||
const MobileChatScreen({
|
||||
super.key,
|
||||
required this.id,
|
||||
this.title,
|
||||
});
|
||||
|
||||
/// view id
|
||||
final String id;
|
||||
final String? title;
|
||||
|
||||
static const routeName = '/chat';
|
||||
static const viewId = 'id';
|
||||
static const viewTitle = 'title';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MobileViewPage(
|
||||
id: id,
|
||||
title: title,
|
||||
viewLayout: ViewLayoutPB.Chat,
|
||||
);
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -15,6 +15,7 @@ import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:appflowy_board/appflowy_board.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -265,7 +266,7 @@ class _BoardContentState extends State<_BoardContent> {
|
||||
BoxDecoration _makeBoxDecoration(BuildContext context) {
|
||||
final themeMode = context.read<AppearanceSettingsCubit>().state.themeMode;
|
||||
return BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
color: AFThemeExtension.of(context).background,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
border: themeMode == ThemeMode.light
|
||||
? Border.fromBorderSide(
|
||||
|
||||
+5
-5
@@ -60,7 +60,7 @@ class _MobileBoardTrailingState extends State<MobileBoardTrailing> {
|
||||
child: IconButton(
|
||||
icon: Icon(
|
||||
Icons.close,
|
||||
color: style.colorScheme.onBackground,
|
||||
color: style.colorScheme.onSurface,
|
||||
),
|
||||
onPressed: () =>
|
||||
setState(() => _textController.clear()),
|
||||
@@ -86,7 +86,7 @@ class _MobileBoardTrailingState extends State<MobileBoardTrailing> {
|
||||
child: Text(
|
||||
LocaleKeys.button_cancel.tr(),
|
||||
style: style.textTheme.titleSmall?.copyWith(
|
||||
color: style.colorScheme.onBackground,
|
||||
color: style.colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
onPressed: () => setState(() => isEditing = false),
|
||||
@@ -96,7 +96,7 @@ class _MobileBoardTrailingState extends State<MobileBoardTrailing> {
|
||||
LocaleKeys.button_add.tr(),
|
||||
style: style.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: style.colorScheme.onBackground,
|
||||
color: style.colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
@@ -117,14 +117,14 @@ class _MobileBoardTrailingState extends State<MobileBoardTrailing> {
|
||||
)
|
||||
: ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
foregroundColor: style.colorScheme.onBackground,
|
||||
foregroundColor: style.colorScheme.onSurface,
|
||||
backgroundColor: style.colorScheme.secondary,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
).copyWith(
|
||||
overlayColor:
|
||||
MaterialStateProperty.all(Theme.of(context).hoverColor),
|
||||
WidgetStateProperty.all(Theme.of(context).hoverColor),
|
||||
),
|
||||
icon: const Icon(Icons.add),
|
||||
label: Text(
|
||||
|
||||
+2
-1
@@ -9,6 +9,7 @@ import 'package:appflowy/plugins/database/widgets/cell/card_cell_skeleton/text_c
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -168,7 +169,7 @@ class MobileHiddenGroup extends StatelessWidget {
|
||||
return TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
textStyle: Theme.of(context).textTheme.bodyMedium,
|
||||
foregroundColor: Theme.of(context).colorScheme.onBackground,
|
||||
foregroundColor: AFThemeExtension.of(context).onBackground,
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
child: CardCellBuilder(
|
||||
|
||||
+4
-3
@@ -20,6 +20,7 @@ import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'
|
||||
import 'package:appflowy/plugins/database/widgets/row/row_property.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -131,7 +132,7 @@ class _MobileRowDetailPageState extends State<MobileRowDetailPage> {
|
||||
void _showCardActions(BuildContext context) {
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
backgroundColor: AFThemeExtension.of(context).background,
|
||||
showDragHandle: true,
|
||||
builder: (_) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@@ -139,7 +140,7 @@ class _MobileRowDetailPageState extends State<MobileRowDetailPage> {
|
||||
MobileQuickActionButton(
|
||||
onTap: () =>
|
||||
_performAction(viewId, _bloc.state.currentRowId, false),
|
||||
icon: FlowySvgs.copy_s,
|
||||
icon: FlowySvgs.duplicate_s,
|
||||
text: LocaleKeys.button_duplicate.tr(),
|
||||
),
|
||||
const Divider(height: 8.5, thickness: 0.5),
|
||||
@@ -147,7 +148,7 @@ class _MobileRowDetailPageState extends State<MobileRowDetailPage> {
|
||||
onTap: () => _performAction(viewId, _bloc.state.currentRowId, true),
|
||||
text: LocaleKeys.button_delete.tr(),
|
||||
textColor: Theme.of(context).colorScheme.error,
|
||||
icon: FlowySvgs.m_delete_m,
|
||||
icon: FlowySvgs.trash_s,
|
||||
iconColor: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
const Divider(height: 8.5, thickness: 0.5),
|
||||
|
||||
+8
-4
@@ -2,6 +2,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/database/field/mobile_field_bottom_sheets.dart';
|
||||
import 'package:appflowy/plugins/database/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -19,20 +20,23 @@ class MobileRowDetailCreateFieldButton extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(minWidth: double.infinity),
|
||||
constraints: BoxConstraints(
|
||||
minWidth: double.infinity,
|
||||
minHeight: GridSize.headerHeight,
|
||||
),
|
||||
child: TextButton.icon(
|
||||
style: Theme.of(context).textButtonTheme.style?.copyWith(
|
||||
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
|
||||
shape: WidgetStateProperty.all<RoundedRectangleBorder>(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
),
|
||||
overlayColor: MaterialStateProperty.all<Color>(
|
||||
overlayColor: WidgetStateProperty.all<Color>(
|
||||
Theme.of(context).hoverColor,
|
||||
),
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
splashFactory: NoSplash.splashFactory,
|
||||
padding: const MaterialStatePropertyAll(
|
||||
padding: const WidgetStatePropertyAll(
|
||||
EdgeInsets.symmetric(vertical: 14, horizontal: 6),
|
||||
),
|
||||
),
|
||||
|
||||
+5
-2
@@ -1,3 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/type_option_menu_item.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
@@ -7,7 +9,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.da
|
||||
import 'package:appflowy/util/field_type_extension.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'mobile_create_field_screen.dart';
|
||||
@@ -27,6 +29,7 @@ const mobileSupportedFieldTypes = [
|
||||
FieldType.CreatedTime,
|
||||
FieldType.Checkbox,
|
||||
FieldType.Checklist,
|
||||
// FieldType.Time,
|
||||
];
|
||||
|
||||
Future<FieldType?> showFieldTypeGridBottomSheet(
|
||||
@@ -40,7 +43,7 @@ Future<FieldType?> showFieldTypeGridBottomSheet(
|
||||
showCloseButton: true,
|
||||
elevation: 20,
|
||||
title: title,
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
backgroundColor: AFThemeExtension.of(context).background,
|
||||
enableDraggableScrollable: true,
|
||||
builder: (context) {
|
||||
final typeOptionMenuItemValue = mobileSupportedFieldTypes
|
||||
|
||||
+1
@@ -119,6 +119,7 @@ class FieldOptionValues {
|
||||
case FieldType.RichText:
|
||||
case FieldType.URL:
|
||||
case FieldType.Checkbox:
|
||||
case FieldType.Time:
|
||||
return null;
|
||||
case FieldType.Number:
|
||||
return NumberTypeOptionPB(
|
||||
|
||||
+1
-1
@@ -5,8 +5,8 @@ import 'package:appflowy/mobile/presentation/database/field/mobile_field_bottom_
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/plugins/database/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database/application/field/field_editor_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/domain/field_backend_service.dart';
|
||||
import 'package:appflowy/plugins/database/application/field/field_info.dart';
|
||||
import 'package:appflowy/plugins/database/domain/field_backend_service.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/setting/field_visibility_extension.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
+1
-1
@@ -438,7 +438,7 @@ class _SortDetailContent extends StatelessWidget {
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
),
|
||||
splashFactory: NoSplash.splashFactory,
|
||||
overlayColor: const MaterialStatePropertyAll(
|
||||
overlayColor: const WidgetStatePropertyAll(
|
||||
Colors.transparent,
|
||||
),
|
||||
onTap: (index) {
|
||||
|
||||
+2
-1
@@ -11,6 +11,7 @@ import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -183,7 +184,7 @@ class MobileDatabaseViewListButton extends StatelessWidget {
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
showDragHandle: true,
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
backgroundColor: AFThemeExtension.of(context).background,
|
||||
builder: (_) {
|
||||
return BlocProvider<ViewBloc>(
|
||||
create: (_) =>
|
||||
|
||||
+3
-3
@@ -107,9 +107,9 @@ enum _Action {
|
||||
|
||||
FlowySvgData get icon {
|
||||
return switch (this) {
|
||||
edit => FlowySvgs.edit_s,
|
||||
duplicate => FlowySvgs.copy_s,
|
||||
delete => FlowySvgs.delete_s,
|
||||
edit => FlowySvgs.view_item_rename_s,
|
||||
duplicate => FlowySvgs.duplicate_s,
|
||||
delete => FlowySvgs.trash_s,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
-1
@@ -234,7 +234,6 @@ class DatabaseViewSettingTile extends StatelessWidget {
|
||||
showHeader: true,
|
||||
showBackButton: true,
|
||||
title: LocaleKeys.grid_settings_properties.tr(),
|
||||
showDivider: true,
|
||||
builder: (_) {
|
||||
return BlocProvider.value(
|
||||
value: context.read<ViewBloc>(),
|
||||
|
||||
+6
-5
@@ -1,5 +1,3 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/application/mobile_router.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/favorite_folder/mobile_home_favorite_folder.dart';
|
||||
@@ -10,6 +8,7 @@ import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||
|
||||
@@ -17,14 +16,15 @@ class MobileFavoritePageFolder extends StatelessWidget {
|
||||
const MobileFavoritePageFolder({
|
||||
super.key,
|
||||
required this.userProfile,
|
||||
required this.workspaceId,
|
||||
});
|
||||
|
||||
final UserProfilePB userProfile;
|
||||
final String workspaceId;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final workspaceId =
|
||||
context.read<UserWorkspaceBloc>().state.currentWorkspace?.workspaceId ??
|
||||
'';
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
@@ -67,7 +67,8 @@ class MobileFavoritePageFolder extends StatelessWidget {
|
||||
MobileFavoriteFolder(
|
||||
showHeader: false,
|
||||
forceExpanded: true,
|
||||
views: favoriteState.views,
|
||||
views:
|
||||
favoriteState.views.map((e) => e.item).toList(),
|
||||
),
|
||||
const VSpace(100.0),
|
||||
],
|
||||
|
||||
@@ -64,8 +64,6 @@ class MobileFavoriteScreen extends StatelessWidget {
|
||||
builder: (context, state) {
|
||||
return MobileFavoritePage(
|
||||
userProfile: userProfile,
|
||||
workspaceId: state.currentWorkspace?.workspaceId ??
|
||||
workspaceSetting.workspaceId,
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -81,11 +79,9 @@ class MobileFavoritePage extends StatelessWidget {
|
||||
const MobileFavoritePage({
|
||||
super.key,
|
||||
required this.userProfile,
|
||||
required this.workspaceId,
|
||||
});
|
||||
|
||||
final UserProfilePB userProfile;
|
||||
final String workspaceId;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -108,7 +104,6 @@ class MobileFavoritePage extends StatelessWidget {
|
||||
Expanded(
|
||||
child: MobileFavoritePageFolder(
|
||||
userProfile: userProfile,
|
||||
workspaceId: workspaceId,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
import 'package:appflowy/mobile/application/mobile_router.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/shared/empty_placeholder.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/shared/mobile_page_card.dart';
|
||||
import 'package:appflowy/util/theme_extension.dart';
|
||||
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/user/prelude.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class MobileFavoriteSpace extends StatefulWidget {
|
||||
const MobileFavoriteSpace({
|
||||
super.key,
|
||||
required this.userProfile,
|
||||
});
|
||||
|
||||
final UserProfilePB userProfile;
|
||||
|
||||
@override
|
||||
State<MobileFavoriteSpace> createState() => _MobileFavoriteSpaceState();
|
||||
}
|
||||
|
||||
class _MobileFavoriteSpaceState extends State<MobileFavoriteSpace>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
final workspaceId =
|
||||
context.read<UserWorkspaceBloc>().state.currentWorkspace?.workspaceId ??
|
||||
'';
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
create: (_) => SidebarSectionsBloc()
|
||||
..add(
|
||||
SidebarSectionsEvent.initial(widget.userProfile, workspaceId),
|
||||
),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (_) => FavoriteBloc()..add(const FavoriteEvent.initial()),
|
||||
),
|
||||
],
|
||||
child: BlocListener<UserWorkspaceBloc, UserWorkspaceState>(
|
||||
listener: (context, state) =>
|
||||
context.read<FavoriteBloc>().add(const FavoriteEvent.initial()),
|
||||
child: MultiBlocListener(
|
||||
listeners: [
|
||||
BlocListener<SidebarSectionsBloc, SidebarSectionsState>(
|
||||
listenWhen: (p, c) =>
|
||||
p.lastCreatedRootView?.id != c.lastCreatedRootView?.id,
|
||||
listener: (context, state) =>
|
||||
context.pushView(state.lastCreatedRootView!),
|
||||
),
|
||||
],
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
final favoriteState = context.watch<FavoriteBloc>().state;
|
||||
|
||||
if (favoriteState.isLoading) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
if (favoriteState.views.isEmpty) {
|
||||
return const EmptySpacePlaceholder(
|
||||
type: MobilePageCardType.favorite,
|
||||
);
|
||||
}
|
||||
|
||||
return _FavoriteViews(
|
||||
favoriteViews: favoriteState.views.reversed.toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _FavoriteViews extends StatelessWidget {
|
||||
const _FavoriteViews({
|
||||
required this.favoriteViews,
|
||||
});
|
||||
|
||||
final List<SectionViewPB> favoriteViews;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final borderColor = Theme.of(context).isLightMode
|
||||
? const Color(0xFFE9E9EC)
|
||||
: const Color(0x1AFFFFFF);
|
||||
return Scrollbar(
|
||||
child: ListView.separated(
|
||||
key: const PageStorageKey('favorite_views_page_storage_key'),
|
||||
padding: EdgeInsets.only(
|
||||
left: HomeSpaceViewSizes.mHorizontalPadding,
|
||||
right: HomeSpaceViewSizes.mHorizontalPadding,
|
||||
bottom: HomeSpaceViewSizes.mVerticalPadding +
|
||||
MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final view = favoriteViews[index];
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 24.0),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: borderColor,
|
||||
width: 0.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: MobileViewPage(
|
||||
key: ValueKey(view.item.id),
|
||||
view: view.item,
|
||||
timestamp: view.timestamp,
|
||||
type: MobilePageCardType.favorite,
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, index) => const HSpace(8),
|
||||
itemCount: favoriteViews.length,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+14
-11
@@ -1,5 +1,3 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/mobile/application/mobile_router.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/default_mobile_action_pane.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/favorite_folder/mobile_home_favorite_folder_header.dart';
|
||||
@@ -7,6 +5,7 @@ import 'package:appflowy/mobile/presentation/page_item/mobile_view_item.dart';
|
||||
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class MobileFavoriteFolder extends StatelessWidget {
|
||||
@@ -28,7 +27,7 @@ class MobileFavoriteFolder extends StatelessWidget {
|
||||
}
|
||||
|
||||
return BlocProvider<FolderBloc>(
|
||||
create: (context) => FolderBloc(type: FolderCategoryType.favorite)
|
||||
create: (context) => FolderBloc(type: FolderSpaceType.favorite)
|
||||
..add(
|
||||
const FolderEvent.initial(),
|
||||
),
|
||||
@@ -55,21 +54,25 @@ class MobileFavoriteFolder extends StatelessWidget {
|
||||
...views.map(
|
||||
(view) => MobileViewItem(
|
||||
key: ValueKey(
|
||||
'${FolderCategoryType.favorite.name} ${view.id}',
|
||||
'${FolderSpaceType.favorite.name} ${view.id}',
|
||||
),
|
||||
categoryType: FolderCategoryType.favorite,
|
||||
spaceType: FolderSpaceType.favorite,
|
||||
isDraggable: false,
|
||||
isFirstChild: view.id == views.first.id,
|
||||
isFeedback: false,
|
||||
view: view,
|
||||
level: 0,
|
||||
onSelected: context.pushView,
|
||||
endActionPane: (context) => buildEndActionPane(context, [
|
||||
view.isFavorite
|
||||
? MobilePaneActionType.removeFromFavorites
|
||||
: MobilePaneActionType.addToFavorites,
|
||||
MobilePaneActionType.more,
|
||||
]),
|
||||
endActionPane: (context) => buildEndActionPane(
|
||||
context,
|
||||
[
|
||||
view.isFavorite
|
||||
? MobilePaneActionType.removeFromFavorites
|
||||
: MobilePaneActionType.addToFavorites,
|
||||
MobilePaneActionType.more,
|
||||
],
|
||||
spaceType: FolderSpaceType.favorite,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import 'package:appflowy/mobile/presentation/home/mobile_folders.dart';
|
||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class MobileHomeSpace extends StatefulWidget {
|
||||
const MobileHomeSpace({super.key, required this.userProfile});
|
||||
|
||||
final UserProfilePB userProfile;
|
||||
|
||||
@override
|
||||
State<MobileHomeSpace> createState() => _MobileHomeSpaceState();
|
||||
}
|
||||
|
||||
class _MobileHomeSpaceState extends State<MobileHomeSpace>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
final workspaceId =
|
||||
context.read<UserWorkspaceBloc>().state.currentWorkspace?.workspaceId ??
|
||||
'';
|
||||
return Scrollbar(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: HomeSpaceViewSizes.mHorizontalPadding,
|
||||
right: HomeSpaceViewSizes.mHorizontalPadding,
|
||||
top: HomeSpaceViewSizes.mVerticalPadding,
|
||||
bottom: HomeSpaceViewSizes.mVerticalPadding +
|
||||
MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
child: MobileFolders(
|
||||
user: widget.userProfile,
|
||||
workspaceId: workspaceId,
|
||||
showFavorite: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/application/mobile_router.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/home.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/section_folder/mobile_home_section_folder.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/space/mobile_space.dart';
|
||||
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@@ -11,6 +15,7 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
// Contains Public And Private Sections
|
||||
class MobileFolders extends StatelessWidget {
|
||||
@@ -31,16 +36,15 @@ class MobileFolders extends StatelessWidget {
|
||||
providers: [
|
||||
BlocProvider(
|
||||
create: (_) => SidebarSectionsBloc()
|
||||
..add(
|
||||
SidebarSectionsEvent.initial(
|
||||
user,
|
||||
workspaceId,
|
||||
),
|
||||
),
|
||||
..add(SidebarSectionsEvent.initial(user, workspaceId)),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (_) => FavoriteBloc()..add(const FavoriteEvent.initial()),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (_) => SpaceBloc()
|
||||
..add(SpaceEvent.initial(user, workspaceId, openFirstPage: false)),
|
||||
),
|
||||
],
|
||||
child: BlocListener<UserWorkspaceBloc, UserWorkspaceState>(
|
||||
listener: (context, state) {
|
||||
@@ -50,50 +54,111 @@ class MobileFolders extends StatelessWidget {
|
||||
state.currentWorkspace?.workspaceId ?? workspaceId,
|
||||
),
|
||||
);
|
||||
context.read<SpaceBloc>().add(
|
||||
SpaceEvent.reset(
|
||||
user,
|
||||
state.currentWorkspace?.workspaceId ?? workspaceId,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: BlocConsumer<SidebarSectionsBloc, SidebarSectionsState>(
|
||||
listenWhen: (p, c) =>
|
||||
p.lastCreatedRootView?.id != c.lastCreatedRootView?.id,
|
||||
listener: (context, state) {
|
||||
final lastCreatedRootView = state.lastCreatedRootView;
|
||||
if (lastCreatedRootView != null) {
|
||||
context.pushView(lastCreatedRootView);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
final isCollaborativeWorkspace =
|
||||
context.read<UserWorkspaceBloc>().state.isCollabWorkspaceOn;
|
||||
return SlidableAutoCloseBehavior(
|
||||
child: Column(
|
||||
children: [
|
||||
...isCollaborativeWorkspace
|
||||
? [
|
||||
MobileSectionFolder(
|
||||
title: LocaleKeys.sideBar_workspace.tr(),
|
||||
categoryType: FolderCategoryType.public,
|
||||
views: state.section.publicViews,
|
||||
),
|
||||
const VSpace(8.0),
|
||||
MobileSectionFolder(
|
||||
title: LocaleKeys.sideBar_private.tr(),
|
||||
categoryType: FolderCategoryType.private,
|
||||
views: state.section.privateViews,
|
||||
),
|
||||
]
|
||||
: [
|
||||
MobileSectionFolder(
|
||||
title: LocaleKeys.sideBar_personal.tr(),
|
||||
categoryType: FolderCategoryType.public,
|
||||
views: state.section.publicViews,
|
||||
),
|
||||
],
|
||||
const VSpace(8.0),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
child: MultiBlocListener(
|
||||
listeners: [
|
||||
BlocListener<SpaceBloc, SpaceState>(
|
||||
listenWhen: (p, c) =>
|
||||
p.lastCreatedPage?.id != c.lastCreatedPage?.id,
|
||||
listener: (context, state) {
|
||||
final lastCreatedPage = state.lastCreatedPage;
|
||||
if (lastCreatedPage != null) {
|
||||
context.pushView(lastCreatedPage);
|
||||
}
|
||||
},
|
||||
),
|
||||
BlocListener<SidebarSectionsBloc, SidebarSectionsState>(
|
||||
listenWhen: (p, c) =>
|
||||
p.lastCreatedRootView?.id != c.lastCreatedRootView?.id,
|
||||
listener: (context, state) {
|
||||
final lastCreatedPage = state.lastCreatedRootView;
|
||||
if (lastCreatedPage != null) {
|
||||
context.pushView(lastCreatedPage);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
child: BlocBuilder<SidebarSectionsBloc, SidebarSectionsState>(
|
||||
builder: (context, state) {
|
||||
return SlidableAutoCloseBehavior(
|
||||
child: Column(
|
||||
children: [
|
||||
..._buildSpaceOrSection(context, state),
|
||||
const VSpace(4.0),
|
||||
const _TrashButton(),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildSpaceOrSection(
|
||||
BuildContext context,
|
||||
SidebarSectionsState state,
|
||||
) {
|
||||
if (context.watch<SpaceBloc>().state.spaces.isNotEmpty) {
|
||||
return [
|
||||
const MobileSpace(),
|
||||
];
|
||||
}
|
||||
|
||||
if (context.read<UserWorkspaceBloc>().state.isCollabWorkspaceOn) {
|
||||
return [
|
||||
MobileSectionFolder(
|
||||
title: LocaleKeys.sideBar_workspace.tr(),
|
||||
spaceType: FolderSpaceType.public,
|
||||
views: state.section.publicViews,
|
||||
),
|
||||
const VSpace(8.0),
|
||||
MobileSectionFolder(
|
||||
title: LocaleKeys.sideBar_private.tr(),
|
||||
spaceType: FolderSpaceType.private,
|
||||
views: state.section.privateViews,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
MobileSectionFolder(
|
||||
title: LocaleKeys.sideBar_personal.tr(),
|
||||
spaceType: FolderSpaceType.public,
|
||||
views: state.section.publicViews,
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class _TrashButton extends StatelessWidget {
|
||||
const _TrashButton();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 52,
|
||||
child: FlowyButton(
|
||||
expand: true,
|
||||
margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 2.0),
|
||||
leftIcon: const FlowySvg(
|
||||
FlowySvgs.m_delete_s,
|
||||
),
|
||||
leftIconSize: const Size.square(18),
|
||||
iconPadding: 10.0,
|
||||
text: FlowyText.regular(
|
||||
LocaleKeys.trash_text.tr(),
|
||||
fontSize: 16.0,
|
||||
),
|
||||
onTap: () => context.push(MobileHomeTrashPage.routeName),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,24 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/home.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/mobile_folders.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/mobile/presentation/home/mobile_home_page_header.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/recent_folder/mobile_home_recent_views.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/tab/mobile_space_tab.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/tab/space_order_bloc.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/recent/cached_recent_service.dart';
|
||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/errors/workspace_failed_screen.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class MobileHomeScreen extends StatelessWidget {
|
||||
@@ -58,6 +59,7 @@ class MobileHomeScreen extends StatelessWidget {
|
||||
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
bottom: false,
|
||||
child: Provider.value(
|
||||
value: userProfile,
|
||||
child: MobileHomePage(
|
||||
@@ -72,7 +74,7 @@ class MobileHomeScreen extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class MobileHomePage extends StatelessWidget {
|
||||
class MobileHomePage extends StatefulWidget {
|
||||
const MobileHomePage({
|
||||
super.key,
|
||||
required this.userProfile,
|
||||
@@ -82,68 +84,70 @@ class MobileHomePage extends StatelessWidget {
|
||||
final UserProfilePB userProfile;
|
||||
final WorkspaceSettingPB workspaceSetting;
|
||||
|
||||
@override
|
||||
State<MobileHomePage> createState() => _MobileHomePageState();
|
||||
}
|
||||
|
||||
class _MobileHomePageState extends State<MobileHomePage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
getIt<MenuSharedState>().addLatestViewListener(_onLatestViewChange);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
getIt<MenuSharedState>().removeLatestViewListener(_onLatestViewChange);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (_) => UserWorkspaceBloc(userProfile: userProfile)
|
||||
..add(
|
||||
const UserWorkspaceEvent.initial(),
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
create: (_) => UserWorkspaceBloc(userProfile: widget.userProfile)
|
||||
..add(const UserWorkspaceEvent.initial()),
|
||||
),
|
||||
child: BlocBuilder<UserWorkspaceBloc, UserWorkspaceState>(
|
||||
BlocProvider(
|
||||
create: (context) =>
|
||||
FavoriteBloc()..add(const FavoriteEvent.initial()),
|
||||
),
|
||||
BlocProvider.value(
|
||||
value: getIt<ReminderBloc>()..add(const ReminderEvent.started()),
|
||||
),
|
||||
],
|
||||
child: BlocConsumer<UserWorkspaceBloc, UserWorkspaceState>(
|
||||
buildWhen: (previous, current) =>
|
||||
previous.currentWorkspace?.workspaceId !=
|
||||
current.currentWorkspace?.workspaceId,
|
||||
listener: (context, state) => getIt<CachedRecentService>().reset(),
|
||||
builder: (context, state) {
|
||||
if (state.currentWorkspace == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
// Header
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 16,
|
||||
right: 16,
|
||||
left: HomeSpaceViewSizes.mHorizontalPadding,
|
||||
right: 8.0,
|
||||
top: Platform.isAndroid ? 8.0 : 0.0,
|
||||
),
|
||||
child: MobileHomePageHeader(
|
||||
userProfile: userProfile,
|
||||
userProfile: widget.userProfile,
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
|
||||
// Folder
|
||||
Expanded(
|
||||
child: Scrollbar(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Recent files
|
||||
const MobileRecentFolder(),
|
||||
|
||||
// Folders
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: MobileFolders(
|
||||
user: userProfile,
|
||||
workspaceId:
|
||||
state.currentWorkspace?.workspaceId ??
|
||||
workspaceSetting.workspaceId,
|
||||
showFavorite: false,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||
child: _TrashButton(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: BlocProvider(
|
||||
create: (context) =>
|
||||
SpaceOrderBloc()..add(const SpaceOrderEvent.initial()),
|
||||
child: MobileSpaceTab(
|
||||
userProfile: widget.userProfile,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -153,26 +157,12 @@ class MobileHomePage extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TrashButton extends StatelessWidget {
|
||||
const _TrashButton();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FlowyButton(
|
||||
expand: true,
|
||||
margin: const EdgeInsets.symmetric(vertical: 8),
|
||||
leftIcon: FlowySvg(
|
||||
FlowySvgs.m_delete_m,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
leftIconSize: const Size.square(24),
|
||||
text: FlowyText.medium(
|
||||
LocaleKeys.trash_text.tr(),
|
||||
fontSize: 18.0,
|
||||
),
|
||||
onTap: () => context.push(MobileHomeTrashPage.routeName),
|
||||
);
|
||||
void _onLatestViewChange() async {
|
||||
final id = getIt<MenuSharedState>().latestOpenView?.id;
|
||||
if (id == null) {
|
||||
return;
|
||||
}
|
||||
await FolderEventSetLatestView(ViewIdPB(value: id)).send();
|
||||
}
|
||||
}
|
||||
+20
-34
@@ -1,5 +1,3 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
@@ -15,6 +13,7 @@ import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sid
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
@@ -36,7 +35,7 @@ class MobileHomePageHeader extends StatelessWidget {
|
||||
final isCollaborativeWorkspace =
|
||||
context.read<UserWorkspaceBloc>().state.isCollabWorkspaceOn;
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(minHeight: 52),
|
||||
constraints: const BoxConstraints(minHeight: 56),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
@@ -45,11 +44,14 @@ class MobileHomePageHeader extends StatelessWidget {
|
||||
? _MobileWorkspace(userProfile: userProfile)
|
||||
: _MobileUser(userProfile: userProfile),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => context.push(
|
||||
GestureDetector(
|
||||
onTap: () => context.push(
|
||||
MobileHomeSettingPage.routeName,
|
||||
),
|
||||
icon: const FlowySvg(FlowySvgs.m_setting_m),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: FlowySvg(FlowySvgs.m_setting_m),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -120,13 +122,14 @@ class _MobileWorkspace extends StatelessWidget {
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
const HSpace(2.0),
|
||||
SizedBox.square(
|
||||
dimension: 34.0,
|
||||
dimension: currentWorkspace.icon.isNotEmpty ? 34.0 : 26.0,
|
||||
child: WorkspaceIcon(
|
||||
workspace: currentWorkspace,
|
||||
iconSize: 26,
|
||||
fontSize: 16.0,
|
||||
enableEdit: false,
|
||||
alignment: Alignment.centerLeft,
|
||||
onSelected: (result) => context.read<UserWorkspaceBloc>().add(
|
||||
UserWorkspaceEvent.updateWorkspaceIcon(
|
||||
currentWorkspace.workspaceId,
|
||||
@@ -135,32 +138,13 @@ class _MobileWorkspace extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
const HSpace(8),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
FlowyText.medium(
|
||||
currentWorkspace.name,
|
||||
fontSize: 16.0,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const HSpace(4.0),
|
||||
const FlowySvg(FlowySvgs.list_dropdown_s),
|
||||
],
|
||||
),
|
||||
FlowyText.medium(
|
||||
userProfile.email.isNotEmpty
|
||||
? userProfile.email
|
||||
: userProfile.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
],
|
||||
),
|
||||
currentWorkspace.icon.isNotEmpty
|
||||
? const HSpace(2)
|
||||
: const HSpace(8),
|
||||
FlowyText.semibold(
|
||||
currentWorkspace.name,
|
||||
fontSize: 16.0,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -177,7 +161,9 @@ class _MobileWorkspace extends StatelessWidget {
|
||||
showDivider: false,
|
||||
showHeader: true,
|
||||
showDragHandle: true,
|
||||
showCloseButton: true,
|
||||
title: LocaleKeys.workspace_menuTitle.tr(),
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
builder: (_) {
|
||||
return BlocProvider.value(
|
||||
value: context.read<UserWorkspaceBloc>(),
|
||||
|
||||
@@ -178,7 +178,7 @@ class _DeletedFilesListView extends StatelessWidget {
|
||||
title: Text(
|
||||
deletedFile.name,
|
||||
style: theme.textTheme.labelMedium
|
||||
?.copyWith(color: theme.colorScheme.onBackground),
|
||||
?.copyWith(color: theme.colorScheme.onSurface),
|
||||
),
|
||||
horizontalTitleGap: 0,
|
||||
tileColor: theme.colorScheme.onSurface.withOpacity(0.1),
|
||||
|
||||
+5
-4
@@ -1,5 +1,3 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
@@ -9,7 +7,9 @@ import 'package:appflowy/workspace/application/recent/prelude.dart';
|
||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
@@ -38,7 +38,8 @@ class _MobileRecentFolderState extends State<MobileRecentFolder> {
|
||||
builder: (context, state) {
|
||||
final ids = <String>{};
|
||||
|
||||
List<ViewPB> recentViews = state.views.reversed.toList();
|
||||
List<ViewPB> recentViews =
|
||||
state.views.reversed.map((e) => e.item).toList();
|
||||
recentViews.retainWhere((element) => ids.add(element.id));
|
||||
|
||||
// only keep the first 20 items.
|
||||
@@ -91,7 +92,7 @@ class _RecentViews extends StatelessWidget {
|
||||
context,
|
||||
showDivider: false,
|
||||
showDragHandle: true,
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
backgroundColor: AFThemeExtension.of(context).background,
|
||||
builder: (_) {
|
||||
return Column(
|
||||
children: [
|
||||
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
import 'package:appflowy/mobile/presentation/home/shared/empty_placeholder.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/shared/mobile_page_card.dart';
|
||||
import 'package:appflowy/util/theme_extension.dart';
|
||||
import 'package:appflowy/workspace/application/recent/prelude.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||
|
||||
class MobileRecentSpace extends StatefulWidget {
|
||||
const MobileRecentSpace({super.key});
|
||||
|
||||
@override
|
||||
State<MobileRecentSpace> createState() => _MobileRecentSpaceState();
|
||||
}
|
||||
|
||||
class _MobileRecentSpaceState extends State<MobileRecentSpace>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return BlocProvider(
|
||||
create: (context) =>
|
||||
RecentViewsBloc()..add(const RecentViewsEvent.initial()),
|
||||
child: BlocBuilder<RecentViewsBloc, RecentViewsState>(
|
||||
builder: (context, state) {
|
||||
if (state.isLoading) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final recentViews = _filterRecentViews(state.views);
|
||||
|
||||
if (recentViews.isEmpty) {
|
||||
return const Center(
|
||||
child: EmptySpacePlaceholder(type: MobilePageCardType.recent),
|
||||
);
|
||||
}
|
||||
|
||||
return _RecentViews(recentViews: recentViews);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<SectionViewPB> _filterRecentViews(List<SectionViewPB> recentViews) {
|
||||
final ids = <String>{};
|
||||
final filteredRecentViews = recentViews.reversed.toList();
|
||||
filteredRecentViews.retainWhere((e) => ids.add(e.item.id));
|
||||
return filteredRecentViews;
|
||||
}
|
||||
}
|
||||
|
||||
class _RecentViews extends StatelessWidget {
|
||||
const _RecentViews({
|
||||
required this.recentViews,
|
||||
});
|
||||
|
||||
final List<SectionViewPB> recentViews;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final borderColor = Theme.of(context).isLightMode
|
||||
? const Color(0xFFE9E9EC)
|
||||
: const Color(0x1AFFFFFF);
|
||||
return SlidableAutoCloseBehavior(
|
||||
child: Scrollbar(
|
||||
child: ListView.separated(
|
||||
key: const PageStorageKey('recent_views_page_storage_key'),
|
||||
padding: EdgeInsets.only(
|
||||
left: HomeSpaceViewSizes.mHorizontalPadding,
|
||||
right: HomeSpaceViewSizes.mHorizontalPadding,
|
||||
bottom: HomeSpaceViewSizes.mVerticalPadding +
|
||||
MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final sectionView = recentViews[index];
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 24.0),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: borderColor,
|
||||
width: 0.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: MobileViewPage(
|
||||
key: ValueKey(sectionView.item.id),
|
||||
view: sectionView.item,
|
||||
timestamp: sectionView.timestamp,
|
||||
type: MobilePageCardType.recent,
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, index) => const HSpace(8),
|
||||
itemCount: recentViews.length,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+40
-39
@@ -1,5 +1,3 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/application/mobile_router.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/default_mobile_action_pane.dart';
|
||||
@@ -8,9 +6,10 @@ import 'package:appflowy/mobile/presentation/page_item/mobile_view_item.dart';
|
||||
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class MobileSectionFolder extends StatelessWidget {
|
||||
@@ -18,17 +17,17 @@ class MobileSectionFolder extends StatelessWidget {
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.views,
|
||||
required this.categoryType,
|
||||
required this.spaceType,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final List<ViewPB> views;
|
||||
final FolderCategoryType categoryType;
|
||||
final FolderSpaceType spaceType;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<FolderBloc>(
|
||||
create: (context) => FolderBloc(type: categoryType)
|
||||
create: (context) => FolderBloc(type: spaceType)
|
||||
..add(
|
||||
const FolderEvent.initial(),
|
||||
),
|
||||
@@ -36,52 +35,54 @@ class MobileSectionFolder extends StatelessWidget {
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
children: [
|
||||
MobileSectionFolderHeader(
|
||||
title: title,
|
||||
isExpanded: context.read<FolderBloc>().state.isExpanded,
|
||||
onPressed: () => context
|
||||
.read<FolderBloc>()
|
||||
.add(const FolderEvent.expandOrUnExpand()),
|
||||
onAdded: () {
|
||||
context.read<SidebarSectionsBloc>().add(
|
||||
SidebarSectionsEvent.createRootViewInSection(
|
||||
name:
|
||||
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||
index: 0,
|
||||
viewSection: categoryType.toViewSectionPB,
|
||||
),
|
||||
);
|
||||
context.read<FolderBloc>().add(
|
||||
const FolderEvent.expandOrUnExpand(isExpanded: true),
|
||||
);
|
||||
},
|
||||
),
|
||||
const VSpace(8.0),
|
||||
const Divider(
|
||||
height: 1,
|
||||
SizedBox(
|
||||
height: HomeSpaceViewSizes.mViewHeight,
|
||||
child: MobileSectionFolderHeader(
|
||||
title: title,
|
||||
isExpanded: context.read<FolderBloc>().state.isExpanded,
|
||||
onPressed: () => context
|
||||
.read<FolderBloc>()
|
||||
.add(const FolderEvent.expandOrUnExpand()),
|
||||
onAdded: () {
|
||||
context.read<SidebarSectionsBloc>().add(
|
||||
SidebarSectionsEvent.createRootViewInSection(
|
||||
name: LocaleKeys.menuAppHeader_defaultNewPageName
|
||||
.tr(),
|
||||
index: 0,
|
||||
viewSection: spaceType.toViewSectionPB,
|
||||
),
|
||||
);
|
||||
context.read<FolderBloc>().add(
|
||||
const FolderEvent.expandOrUnExpand(isExpanded: true),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (state.isExpanded)
|
||||
...views.map(
|
||||
(view) => MobileViewItem(
|
||||
key: ValueKey(
|
||||
'${FolderCategoryType.private.name} ${view.id}',
|
||||
'${FolderSpaceType.private.name} ${view.id}',
|
||||
),
|
||||
categoryType: categoryType,
|
||||
spaceType: spaceType,
|
||||
isFirstChild: view.id == views.first.id,
|
||||
view: view,
|
||||
level: 0,
|
||||
leftPadding: 16,
|
||||
leftPadding: HomeSpaceViewSizes.leftPadding,
|
||||
isFeedback: false,
|
||||
onSelected: context.pushView,
|
||||
endActionPane: (context) {
|
||||
final view = context.read<ViewBloc>().state.view;
|
||||
return buildEndActionPane(context, [
|
||||
MobilePaneActionType.delete,
|
||||
view.isFavorite
|
||||
? MobilePaneActionType.removeFromFavorites
|
||||
: MobilePaneActionType.addToFavorites,
|
||||
MobilePaneActionType.more,
|
||||
]);
|
||||
return buildEndActionPane(
|
||||
context,
|
||||
[
|
||||
MobilePaneActionType.more,
|
||||
if (view.layout == ViewLayoutPB.Document)
|
||||
MobilePaneActionType.add,
|
||||
],
|
||||
spaceType: spaceType,
|
||||
needSpace: false,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
+10
-12
@@ -1,4 +1,5 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -29,24 +30,23 @@ class _MobileSectionFolderHeaderState extends State<MobileSectionFolderHeader> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const iconSize = 32.0;
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: FlowyButton(
|
||||
text: FlowyText.semibold(
|
||||
text: FlowyText.medium(
|
||||
widget.title,
|
||||
fontSize: 20.0,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
margin: const EdgeInsets.symmetric(vertical: 8),
|
||||
margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 2.0),
|
||||
expandText: false,
|
||||
iconPadding: 2,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
rightIcon: AnimatedRotation(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
turns: _turns,
|
||||
child: const Icon(
|
||||
Icons.keyboard_arrow_down_rounded,
|
||||
color: Colors.grey,
|
||||
child: const FlowySvg(
|
||||
FlowySvgs.m_spaces_expand_s,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
@@ -60,12 +60,10 @@ class _MobileSectionFolderHeaderState extends State<MobileSectionFolderHeader> {
|
||||
FlowyIconButton(
|
||||
key: mobileCreateNewPageButtonKey,
|
||||
hoverColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
iconPadding: const EdgeInsets.all(2),
|
||||
height: iconSize,
|
||||
width: iconSize,
|
||||
height: HomeSpaceViewSizes.mViewButtonDimension,
|
||||
width: HomeSpaceViewSizes.mViewButtonDimension,
|
||||
icon: const FlowySvg(
|
||||
FlowySvgs.add_s,
|
||||
size: Size.square(iconSize),
|
||||
FlowySvgs.m_space_add_s,
|
||||
),
|
||||
onPressed: widget.onAdded,
|
||||
),
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/shared/mobile_page_card.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class EmptySpacePlaceholder extends StatelessWidget {
|
||||
const EmptySpacePlaceholder({super.key, required this.type});
|
||||
|
||||
final MobilePageCardType type;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 48.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const FlowySvg(
|
||||
FlowySvgs.m_empty_page_xl,
|
||||
),
|
||||
const VSpace(16.0),
|
||||
FlowyText.medium(
|
||||
_emptyPageText,
|
||||
fontSize: 18.0,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const VSpace(8.0),
|
||||
FlowyText.regular(
|
||||
_emptyPageSubText,
|
||||
fontSize: 17.0,
|
||||
maxLines: 10,
|
||||
textAlign: TextAlign.center,
|
||||
lineHeight: 1.3,
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String get _emptyPageText => switch (type) {
|
||||
MobilePageCardType.recent => LocaleKeys.sideBar_emptyRecent.tr(),
|
||||
MobilePageCardType.favorite => LocaleKeys.sideBar_emptyFavorite.tr(),
|
||||
};
|
||||
|
||||
String get _emptyPageSubText => switch (type) {
|
||||
MobilePageCardType.recent =>
|
||||
LocaleKeys.sideBar_emptyRecentDescription.tr(),
|
||||
MobilePageCardType.favorite =>
|
||||
LocaleKeys.sideBar_emptyFavoriteDescription.tr(),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,406 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/application/mobile_router.dart';
|
||||
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
|
||||
import 'package:appflowy/mobile/application/recent/recent_view_bloc.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy/shared/appflowy_network_image.dart';
|
||||
import 'package:appflowy/shared/flowy_gradient_colors.dart';
|
||||
import 'package:appflowy/util/string_extension.dart';
|
||||
import 'package:appflowy/util/theme_extension.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.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/view_bloc.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:easy_localization/easy_localization.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:string_validator/string_validator.dart';
|
||||
import 'package:time/time.dart';
|
||||
|
||||
enum MobilePageCardType {
|
||||
recent,
|
||||
favorite;
|
||||
|
||||
String get lastOperationHintText => switch (this) {
|
||||
MobilePageCardType.recent => LocaleKeys.sideBar_lastViewed.tr(),
|
||||
MobilePageCardType.favorite => LocaleKeys.sideBar_favoriteAt.tr(),
|
||||
};
|
||||
}
|
||||
|
||||
class MobileViewPage extends StatelessWidget {
|
||||
const MobileViewPage({
|
||||
super.key,
|
||||
required this.view,
|
||||
this.timestamp,
|
||||
required this.type,
|
||||
});
|
||||
|
||||
final ViewPB view;
|
||||
final Int64? timestamp;
|
||||
final MobilePageCardType type;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<ViewBloc>(
|
||||
create: (context) => ViewBloc(view: view, shouldLoadChildViews: false)
|
||||
..add(const ViewEvent.initial()),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) =>
|
||||
RecentViewBloc(view: view)..add(const RecentViewEvent.initial()),
|
||||
),
|
||||
],
|
||||
child: BlocBuilder<RecentViewBloc, RecentViewState>(
|
||||
builder: (context, state) {
|
||||
return Slidable(
|
||||
endActionPane: buildEndActionPane(
|
||||
context,
|
||||
[
|
||||
MobilePaneActionType.more,
|
||||
context.watch<ViewBloc>().state.view.isFavorite
|
||||
? MobilePaneActionType.removeFromFavorites
|
||||
: MobilePaneActionType.addToFavorites,
|
||||
],
|
||||
cardType: type,
|
||||
),
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTapUp: (_) => context.pushView(view),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(child: _buildDescription(context, state)),
|
||||
const HSpace(20.0),
|
||||
SizedBox(
|
||||
width: 84,
|
||||
height: 60,
|
||||
child: _buildCover(context, state),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDescription(BuildContext context, RecentViewState state) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// page icon & page title
|
||||
_buildTitle(context, state),
|
||||
const VSpace(12.0),
|
||||
// author & last viewed
|
||||
_buildNameAndLastViewed(context, state),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNameAndLastViewed(BuildContext context, RecentViewState state) {
|
||||
final supportAvatar = isURL(state.icon);
|
||||
if (!supportAvatar) {
|
||||
return _buildLastViewed(context);
|
||||
}
|
||||
return Row(
|
||||
children: [
|
||||
_buildAvatar(context, state),
|
||||
Flexible(child: _buildAuthor(context, state)),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 3.0),
|
||||
child: FlowySvg(FlowySvgs.dot_s),
|
||||
),
|
||||
_buildLastViewed(context),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAvatar(BuildContext context, RecentViewState state) {
|
||||
final userProfile = Provider.of<UserProfilePB?>(context);
|
||||
final iconUrl = userProfile?.iconUrl;
|
||||
if (iconUrl == null ||
|
||||
iconUrl.isEmpty ||
|
||||
view.createdBy != userProfile?.id) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 2, bottom: 2, right: 8),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
child: SizedBox.square(
|
||||
dimension: 16.0,
|
||||
child: FlowyNetworkImage(
|
||||
url: iconUrl,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCover(BuildContext context, RecentViewState state) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: _ViewCover(
|
||||
layout: view.layout,
|
||||
coverTypeV1: state.coverTypeV1,
|
||||
coverTypeV2: state.coverTypeV2,
|
||||
value: state.coverValue,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTitle(BuildContext context, RecentViewState state) {
|
||||
final name = state.name;
|
||||
final icon = state.icon;
|
||||
final fontFamily = Platform.isAndroid || Platform.isLinux
|
||||
? GoogleFonts.notoColorEmoji().fontFamily
|
||||
: null;
|
||||
return RichText(
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: icon,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontSize: 17.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontFamily: fontFamily,
|
||||
),
|
||||
),
|
||||
if (icon.isNotEmpty) const WidgetSpan(child: HSpace(2.0)),
|
||||
TextSpan(
|
||||
text: name,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
height: 1.3,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAuthor(BuildContext context, RecentViewState state) {
|
||||
return FlowyText.regular(
|
||||
// view.createdBy.toString(),
|
||||
'Lucas',
|
||||
fontSize: 12.0,
|
||||
color: Theme.of(context).hintColor,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLastViewed(BuildContext context) {
|
||||
final textColor = Theme.of(context).isLightMode
|
||||
? const Color(0xFF171717)
|
||||
: Colors.white.withOpacity(0.45);
|
||||
if (timestamp == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
final date = _formatTimestamp(
|
||||
context,
|
||||
timestamp!.toInt() * 1000,
|
||||
);
|
||||
return FlowyText.regular(
|
||||
date,
|
||||
fontSize: 13.0,
|
||||
color: textColor,
|
||||
);
|
||||
}
|
||||
|
||||
String _formatTimestamp(BuildContext context, int timestamp) {
|
||||
final now = DateTime.now();
|
||||
final dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp);
|
||||
final difference = now.difference(dateTime);
|
||||
final String date;
|
||||
|
||||
final dateFormate =
|
||||
context.read<AppearanceSettingsCubit>().state.dateFormat;
|
||||
final timeFormate =
|
||||
context.read<AppearanceSettingsCubit>().state.timeFormat;
|
||||
|
||||
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 = timeFormate.formatTime(dateTime);
|
||||
} else {
|
||||
date = dateFormate.formatDate(dateTime, false);
|
||||
}
|
||||
|
||||
if (difference.inHours >= 1) {
|
||||
return '${type.lastOperationHintText} $date';
|
||||
}
|
||||
|
||||
return date;
|
||||
}
|
||||
}
|
||||
|
||||
class _ViewCover extends StatelessWidget {
|
||||
const _ViewCover({
|
||||
required this.layout,
|
||||
required this.coverTypeV1,
|
||||
this.coverTypeV2,
|
||||
this.value,
|
||||
});
|
||||
|
||||
final ViewLayoutPB layout;
|
||||
final CoverType coverTypeV1;
|
||||
final PageStyleCoverImageType? coverTypeV2;
|
||||
final String? value;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final placeholder = _buildPlaceholder(context);
|
||||
final value = this.value;
|
||||
if (value == null) {
|
||||
return placeholder;
|
||||
}
|
||||
if (coverTypeV2 != null) {
|
||||
return _buildCoverV2(context, value, placeholder);
|
||||
}
|
||||
return _buildCoverV1(context, value, placeholder);
|
||||
}
|
||||
|
||||
Widget _buildPlaceholder(BuildContext context) {
|
||||
final isLightMode = Theme.of(context).isLightMode;
|
||||
final (svg, color) = switch (layout) {
|
||||
ViewLayoutPB.Document => (
|
||||
FlowySvgs.m_document_thumbnail_m,
|
||||
isLightMode ? const Color(0xCCEDFBFF) : const Color(0x33658B90)
|
||||
),
|
||||
ViewLayoutPB.Grid => (
|
||||
FlowySvgs.m_grid_thumbnail_m,
|
||||
isLightMode ? const Color(0xFFF5F4FF) : const Color(0x338B80AD)
|
||||
),
|
||||
ViewLayoutPB.Board => (
|
||||
FlowySvgs.m_board_thumbnail_m,
|
||||
isLightMode ? const Color(0x7FE0FDD9) : const Color(0x3372936B),
|
||||
),
|
||||
ViewLayoutPB.Calendar => (
|
||||
FlowySvgs.m_calendar_thumbnail_m,
|
||||
isLightMode ? const Color(0xFFFFF7F0) : const Color(0x33A68B77)
|
||||
),
|
||||
ViewLayoutPB.Chat => (
|
||||
FlowySvgs.m_chat_thumbnail_m,
|
||||
isLightMode ? const Color(0x66FFE6FD) : const Color(0x33987195)
|
||||
),
|
||||
_ => (
|
||||
FlowySvgs.m_document_thumbnail_m,
|
||||
isLightMode ? Colors.black : Colors.white
|
||||
)
|
||||
};
|
||||
return ColoredBox(
|
||||
color: color,
|
||||
child: Center(
|
||||
child: FlowySvg(
|
||||
svg,
|
||||
blendMode: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCoverV2(BuildContext context, String value, Widget placeholder) {
|
||||
final type = coverTypeV2;
|
||||
if (type == null) {
|
||||
return placeholder;
|
||||
}
|
||||
if (type == PageStyleCoverImageType.customImage ||
|
||||
type == PageStyleCoverImageType.unsplashImage) {
|
||||
final userProfilePB = Provider.of<UserProfilePB?>(context);
|
||||
return FlowyNetworkImage(
|
||||
url: value,
|
||||
userProfilePB: userProfilePB,
|
||||
);
|
||||
}
|
||||
|
||||
if (type == PageStyleCoverImageType.builtInImage) {
|
||||
return Image.asset(
|
||||
PageStyleCoverImageType.builtInImagePath(value),
|
||||
fit: BoxFit.cover,
|
||||
);
|
||||
}
|
||||
|
||||
if (type == PageStyleCoverImageType.pureColor) {
|
||||
final color = value.coverColor(context);
|
||||
if (color != null) {
|
||||
return ColoredBox(
|
||||
color: color,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (type == PageStyleCoverImageType.gradientColor) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: FlowyGradientColor.fromId(value).linear,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (type == PageStyleCoverImageType.localImage) {
|
||||
return Image.file(
|
||||
File(value),
|
||||
fit: BoxFit.cover,
|
||||
);
|
||||
}
|
||||
|
||||
return placeholder;
|
||||
}
|
||||
|
||||
Widget _buildCoverV1(BuildContext context, String value, Widget placeholder) {
|
||||
switch (coverTypeV1) {
|
||||
case CoverType.file:
|
||||
if (isURL(value)) {
|
||||
final userProfilePB = Provider.of<UserProfilePB?>(context);
|
||||
return FlowyNetworkImage(
|
||||
url: value,
|
||||
userProfilePB: userProfilePB,
|
||||
);
|
||||
}
|
||||
final imageFile = File(value);
|
||||
if (!imageFile.existsSync()) {
|
||||
return placeholder;
|
||||
}
|
||||
return Image.file(
|
||||
imageFile,
|
||||
);
|
||||
case CoverType.asset:
|
||||
return Image.asset(
|
||||
value,
|
||||
fit: BoxFit.cover,
|
||||
);
|
||||
case CoverType.color:
|
||||
final color = value.tryToColor() ?? Colors.white;
|
||||
return Container(
|
||||
color: color,
|
||||
);
|
||||
case CoverType.none:
|
||||
return placeholder;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/application/mobile_router.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/space/mobile_space_header.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/space/mobile_space_menu.dart';
|
||||
import 'package:appflowy/mobile/presentation/page_item/mobile_view_item.dart';
|
||||
import 'package:appflowy/mobile/presentation/presentation.dart';
|
||||
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class MobileSpace extends StatefulWidget {
|
||||
const MobileSpace({super.key});
|
||||
|
||||
@override
|
||||
State<MobileSpace> createState() => _MobileSpaceState();
|
||||
}
|
||||
|
||||
class _MobileSpaceState extends State<MobileSpace> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
createNewPageNotifier.addListener(_createNewPage);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
createNewPageNotifier.removeListener(_createNewPage);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<SpaceBloc, SpaceState>(
|
||||
builder: (context, state) {
|
||||
if (state.spaces.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final currentSpace = state.currentSpace ?? state.spaces.first;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
MobileSpaceHeader(
|
||||
isExpanded: state.isExpanded,
|
||||
space: currentSpace,
|
||||
onAdded: () {
|
||||
context.read<SpaceBloc>().add(
|
||||
SpaceEvent.createPage(
|
||||
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||
layout: ViewLayoutPB.Document,
|
||||
index: 0,
|
||||
),
|
||||
);
|
||||
context.read<SpaceBloc>().add(
|
||||
SpaceEvent.expand(currentSpace, true),
|
||||
);
|
||||
},
|
||||
onPressed: () => _showSpaceMenu(context),
|
||||
),
|
||||
_Pages(
|
||||
key: ValueKey(currentSpace.id),
|
||||
space: currentSpace,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _showSpaceMenu(BuildContext context) {
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
showDivider: false,
|
||||
showHeader: true,
|
||||
showDragHandle: true,
|
||||
showCloseButton: true,
|
||||
showDoneButton: true,
|
||||
title: LocaleKeys.space_title.tr(),
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
builder: (_) {
|
||||
return BlocProvider.value(
|
||||
value: context.read<SpaceBloc>(),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: MobileSpaceMenu(),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _createNewPage() {
|
||||
context.read<SpaceBloc>().add(
|
||||
SpaceEvent.createPage(
|
||||
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||
layout: ViewLayoutPB.Document,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Pages extends StatelessWidget {
|
||||
const _Pages({
|
||||
super.key,
|
||||
required this.space,
|
||||
});
|
||||
|
||||
final ViewPB space;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) =>
|
||||
ViewBloc(view: space)..add(const ViewEvent.initial()),
|
||||
child: BlocBuilder<ViewBloc, ViewState>(
|
||||
builder: (context, state) {
|
||||
final spaceType = space.spacePermission == SpacePermission.publicToAll
|
||||
? FolderSpaceType.public
|
||||
: FolderSpaceType.private;
|
||||
return Column(
|
||||
children: state.view.childViews
|
||||
.map(
|
||||
(view) => MobileViewItem(
|
||||
key: ValueKey(
|
||||
'${space.id} ${view.id}',
|
||||
),
|
||||
spaceType: spaceType,
|
||||
isFirstChild: view.id == state.view.childViews.first.id,
|
||||
view: view,
|
||||
level: 0,
|
||||
leftPadding: HomeSpaceViewSizes.leftPadding,
|
||||
isFeedback: false,
|
||||
onSelected: context.pushView,
|
||||
endActionPane: (context) {
|
||||
final view = context.read<ViewBloc>().state.view;
|
||||
return buildEndActionPane(
|
||||
context,
|
||||
[
|
||||
MobilePaneActionType.more,
|
||||
if (view.layout == ViewLayoutPB.Document)
|
||||
MobilePaneActionType.add,
|
||||
],
|
||||
spaceType: spaceType,
|
||||
needSpace: false,
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@visibleForTesting
|
||||
const Key mobileCreateNewPageButtonKey = Key('mobileCreateNewPageButtonKey');
|
||||
|
||||
class MobileSpaceHeader extends StatelessWidget {
|
||||
const MobileSpaceHeader({
|
||||
super.key,
|
||||
required this.space,
|
||||
required this.onPressed,
|
||||
required this.onAdded,
|
||||
required this.isExpanded,
|
||||
});
|
||||
|
||||
final ViewPB space;
|
||||
final VoidCallback onPressed;
|
||||
final VoidCallback onAdded;
|
||||
final bool isExpanded;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: onPressed,
|
||||
child: SizedBox(
|
||||
height: 48,
|
||||
child: Row(
|
||||
children: [
|
||||
SpaceIcon(
|
||||
dimension: 24,
|
||||
space: space,
|
||||
cornerRadius: 6.0,
|
||||
),
|
||||
const HSpace(8),
|
||||
FlowyText.medium(
|
||||
space.name,
|
||||
lineHeight: 1.15,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
const HSpace(4.0),
|
||||
const FlowySvg(
|
||||
FlowySvgs.workspace_drop_down_menu_show_s,
|
||||
),
|
||||
const Spacer(),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: onAdded,
|
||||
child: const FlowySvg(
|
||||
FlowySvgs.m_space_add_s,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Future<void> _onAction(SpaceMoreActionType type, dynamic data) async {
|
||||
// switch (type) {
|
||||
// case SpaceMoreActionType.rename:
|
||||
// await _showRenameDialog();
|
||||
// break;
|
||||
// case SpaceMoreActionType.changeIcon:
|
||||
// final (String icon, String iconColor) = data;
|
||||
// context.read<SpaceBloc>().add(SpaceEvent.changeIcon(icon, iconColor));
|
||||
// break;
|
||||
// case SpaceMoreActionType.manage:
|
||||
// _showManageSpaceDialog(context);
|
||||
// break;
|
||||
// case SpaceMoreActionType.addNewSpace:
|
||||
// break;
|
||||
// case SpaceMoreActionType.collapseAllPages:
|
||||
// break;
|
||||
// case SpaceMoreActionType.delete:
|
||||
// _showDeleteSpaceDialog(context);
|
||||
// break;
|
||||
// case SpaceMoreActionType.divider:
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
// Future<void> _showRenameDialog() async {
|
||||
// await NavigatorTextFieldDialog(
|
||||
// title: LocaleKeys.space_rename.tr(),
|
||||
// value: space.name,
|
||||
// autoSelectAllText: true,
|
||||
// onConfirm: (name, _) {
|
||||
// context.read<SpaceBloc>().add(SpaceEvent.rename(space, name));
|
||||
// },
|
||||
// ).show(context);
|
||||
// }
|
||||
|
||||
// void _showManageSpaceDialog(BuildContext context) {
|
||||
// final spaceBloc = context.read<SpaceBloc>();
|
||||
// showDialog(
|
||||
// context: context,
|
||||
// builder: (_) {
|
||||
// return Dialog(
|
||||
// shape: RoundedRectangleBorder(
|
||||
// borderRadius: BorderRadius.circular(12.0),
|
||||
// ),
|
||||
// child: BlocProvider.value(
|
||||
// value: spaceBloc,
|
||||
// child: const ManageSpacePopup(),
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
|
||||
// void _showDeleteSpaceDialog(BuildContext context) {
|
||||
// final spaceBloc = context.read<SpaceBloc>();
|
||||
// showDialog(
|
||||
// context: context,
|
||||
// builder: (_) {
|
||||
// return Dialog(
|
||||
// shape: RoundedRectangleBorder(
|
||||
// borderRadius: BorderRadius.circular(12.0),
|
||||
// ),
|
||||
// child: BlocProvider.value(
|
||||
// value: spaceBloc,
|
||||
// child: const SizedBox(width: 440, child: DeleteSpacePopup()),
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class MobileSpaceMenu extends StatelessWidget {
|
||||
const MobileSpaceMenu({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<SpaceBloc, SpaceState>(
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const VSpace(4.0),
|
||||
for (final space in state.spaces)
|
||||
SizedBox(
|
||||
height: 52,
|
||||
child: _SidebarSpaceMenuItem(
|
||||
space: space,
|
||||
isSelected: state.currentSpace?.id == space.id,
|
||||
),
|
||||
),
|
||||
// const Padding(
|
||||
// padding: EdgeInsets.symmetric(vertical: 8.0),
|
||||
// child: Divider(
|
||||
// height: 0.5,
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(
|
||||
// height: 52,
|
||||
// child: _CreateSpaceButton(),
|
||||
// ),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SidebarSpaceMenuItem extends StatelessWidget {
|
||||
const _SidebarSpaceMenuItem({
|
||||
required this.space,
|
||||
required this.isSelected,
|
||||
});
|
||||
|
||||
final ViewPB space;
|
||||
final bool isSelected;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FlowyButton(
|
||||
text: Row(
|
||||
children: [
|
||||
FlowyText.medium(
|
||||
space.name,
|
||||
),
|
||||
const HSpace(6.0),
|
||||
if (space.spacePermission == SpacePermission.private)
|
||||
const FlowySvg(
|
||||
FlowySvgs.space_lock_s,
|
||||
size: Size.square(12),
|
||||
),
|
||||
],
|
||||
),
|
||||
iconPadding: 10,
|
||||
leftIcon: SpaceIcon(
|
||||
dimension: 24,
|
||||
space: space,
|
||||
cornerRadius: 6.0,
|
||||
),
|
||||
leftIconSize: const Size.square(20),
|
||||
rightIcon: isSelected
|
||||
? const FlowySvg(
|
||||
FlowySvgs.workspace_selected_s,
|
||||
blendMode: null,
|
||||
)
|
||||
: null,
|
||||
onTap: () {
|
||||
context.read<SpaceBloc>().add(SpaceEvent.open(space));
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// class _CreateSpaceButton extends StatelessWidget {
|
||||
// const _CreateSpaceButton();
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return FlowyButton(
|
||||
// text: FlowyText.regular(LocaleKeys.space_createNewSpace.tr()),
|
||||
// iconPadding: 10,
|
||||
// leftIcon: const FlowySvg(
|
||||
// FlowySvgs.space_add_s,
|
||||
// ),
|
||||
// leftIconSize: const Size.square(20),
|
||||
// onTap: () {
|
||||
// PopoverContainer.of(context).close();
|
||||
// _showCreateSpaceDialog(context);
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
|
||||
// void _showCreateSpaceDialog(BuildContext context) {
|
||||
// final spaceBloc = context.read<SpaceBloc>();
|
||||
// showDialog(
|
||||
// context: context,
|
||||
// builder: (_) {
|
||||
// return Dialog(
|
||||
// shape: RoundedRectangleBorder(
|
||||
// borderRadius: BorderRadius.circular(12.0),
|
||||
// ),
|
||||
// child: BlocProvider.value(
|
||||
// value: spaceBloc,
|
||||
// child: const CreateSpacePopup(),
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class RoundUnderlineTabIndicator extends Decoration {
|
||||
const RoundUnderlineTabIndicator({
|
||||
this.borderRadius,
|
||||
this.borderSide = const BorderSide(width: 2.0, color: Colors.white),
|
||||
this.insets = EdgeInsets.zero,
|
||||
required this.width,
|
||||
});
|
||||
|
||||
final BorderRadius? borderRadius;
|
||||
final BorderSide borderSide;
|
||||
final EdgeInsetsGeometry insets;
|
||||
final double width;
|
||||
|
||||
@override
|
||||
Decoration? lerpFrom(Decoration? a, double t) {
|
||||
if (a is UnderlineTabIndicator) {
|
||||
return UnderlineTabIndicator(
|
||||
borderSide: BorderSide.lerp(a.borderSide, borderSide, t),
|
||||
insets: EdgeInsetsGeometry.lerp(a.insets, insets, t)!,
|
||||
);
|
||||
}
|
||||
return super.lerpFrom(a, t);
|
||||
}
|
||||
|
||||
@override
|
||||
Decoration? lerpTo(Decoration? b, double t) {
|
||||
if (b is UnderlineTabIndicator) {
|
||||
return UnderlineTabIndicator(
|
||||
borderSide: BorderSide.lerp(borderSide, b.borderSide, t),
|
||||
insets: EdgeInsetsGeometry.lerp(insets, b.insets, t)!,
|
||||
);
|
||||
}
|
||||
return super.lerpTo(b, t);
|
||||
}
|
||||
|
||||
@override
|
||||
BoxPainter createBoxPainter([VoidCallback? onChanged]) {
|
||||
return _UnderlinePainter(this, borderRadius, onChanged);
|
||||
}
|
||||
|
||||
Rect _indicatorRectFor(Rect rect, TextDirection textDirection) {
|
||||
final Rect indicator = insets.resolve(textDirection).deflateRect(rect);
|
||||
final center = indicator.center.dx;
|
||||
return Rect.fromLTWH(
|
||||
center - width / 2.0,
|
||||
indicator.bottom - borderSide.width,
|
||||
width,
|
||||
borderSide.width,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Path getClipPath(Rect rect, TextDirection textDirection) {
|
||||
if (borderRadius != null) {
|
||||
return Path()
|
||||
..addRRect(
|
||||
borderRadius!.toRRect(_indicatorRectFor(rect, textDirection)),
|
||||
);
|
||||
}
|
||||
return Path()..addRect(_indicatorRectFor(rect, textDirection));
|
||||
}
|
||||
}
|
||||
|
||||
class _UnderlinePainter extends BoxPainter {
|
||||
_UnderlinePainter(
|
||||
this.decoration,
|
||||
this.borderRadius,
|
||||
super.onChanged,
|
||||
);
|
||||
|
||||
final RoundUnderlineTabIndicator decoration;
|
||||
final BorderRadius? borderRadius;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
|
||||
assert(configuration.size != null);
|
||||
final Rect rect = offset & configuration.size!;
|
||||
final TextDirection textDirection = configuration.textDirection!;
|
||||
final Paint paint;
|
||||
if (borderRadius != null) {
|
||||
paint = Paint()..color = decoration.borderSide.color;
|
||||
final Rect indicator = decoration._indicatorRectFor(rect, textDirection);
|
||||
final RRect rrect = RRect.fromRectAndCorners(
|
||||
indicator,
|
||||
topLeft: borderRadius!.topLeft,
|
||||
topRight: borderRadius!.topRight,
|
||||
bottomRight: borderRadius!.bottomRight,
|
||||
bottomLeft: borderRadius!.bottomLeft,
|
||||
);
|
||||
canvas.drawRRect(rrect, paint);
|
||||
} else {
|
||||
paint = decoration.borderSide.toPaint()..strokeCap = StrokeCap.round;
|
||||
final Rect indicator = decoration
|
||||
._indicatorRectFor(rect, textDirection)
|
||||
.deflate(decoration.borderSide.width / 2.0);
|
||||
canvas.drawLine(indicator.bottomLeft, indicator.bottomRight, paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loaded 100 of 1261 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user