Compare commits

...
35 Commits
Author SHA1 Message Date
Lucas.Xu 45cbd70bf4 chore: bump version 0.2.8 (#3105) 2023-08-03 15:07:01 +08:00
Lucas.Xu fb9bc359b8 feat: disable moving page into the database (#3107) 2023-08-03 15:06:02 +08:00
Nathan.fooo 135d8a811e chore: fix historical user open issue (#3106) 2023-08-03 12:07:42 +08:00
Nathan.fooo 03518c951c ci: disable document test (#3104) 2023-08-03 10:33:25 +08:00
Nathan.fooo 03b8f2ccb2 feat: Get started doc migration (#3102)
* feat: migrate empty document

* chore: update collab rev

* chore: fmt
2023-08-03 09:14:52 +08:00
Lucas.Xu a40c639a96 fix: dispose the listener when the favorite bloc dispose (#3103) 2023-08-03 08:50:56 +08:00
Nathan.foooandMathias Mogensen ea0c4e96d2 feat: implement magic link login (#3086)
* feat: implement magic link login

* ci: create env file

* ci: generate flutter env files

* ci: disable inject env

* chore: update table name

* Update frontend/appflowy_flutter/lib/env/env.dart

Co-authored-by: Mathias Mogensen <42929161+Xazin@users.noreply.github.com>

* chore: fix compile

---------

Co-authored-by: Mathias Mogensen <42929161+Xazin@users.noreply.github.com>
2023-08-03 08:48:04 +08:00
Mihir a1143e24f3 feat: support favorites folder 2023-08-02 21:20:51 +08:00
Richard Shiue ff79635b2b fix: height for calendar view in document (#3073) 2023-08-02 18:07:44 +08:00
Lucas.Xu f0d2cf7da3 fix: insert reference page in nested page 2023-08-01 21:47:20 +08:00
Richard Shiue 5e2ed56f5b fix: move translation jsons to right folder (#3050) 2023-08-01 14:19:45 +08:00
Mayur Mahajan 70963a55f1 chore: add c/cpp/auto options in code langauges 2023-08-01 14:16:01 +08:00
Lucas.Xu 62be81395d fix: missing c++ library when building iOS (#3092) 2023-08-01 14:15:19 +08:00
Vedant Pandey 7c21f61d2e feat: add duplicate in context menu (#2921) 2023-08-01 11:19:48 +08:00
Lucas.Xu ad4c68a353 chore: update iOS flowy infra UI name (#3091) 2023-08-01 10:07:12 +08:00
Yijing Huang 355061d415 fix: fix theme color (#3089) 2023-07-31 23:01:24 +08:00
Yijing Huang 3039944c74 feat: welcome screen UI improvement (#3057) 2023-07-31 23:01:12 +08:00
Yijing Huang 338e342410 feat: lavender theme refactor 2023-07-31 20:22:56 +08:00
Aman Negi 8c544bc8c4 fix: improve readability in callout 2023-07-31 20:18:43 +08:00
Lucas.Xu 266209caeb feat: implement draggable folder (#3083) 2023-07-31 20:06:01 +08:00
Kilu.He eb77346e5a feat: support add cover and icon in tauri document (#3069)
* feat: support add cover and icon

* feat: emoji picker

* feat: emoji picker
2023-07-31 11:39:44 +08:00
dejvizelo f28c5d849c feat: clear date value (#2700)
* chore: Add _ClearDateFieldButton

* chore: Add new DateCellCalendarEvent to clear date

* chore: Add grid_field_clearDate translation token

* refactor: _ClearDateFieldButton -> _ClearDateButton

* fix: Fix dart(unused_element) in _ClearDateButton

* feat: Handle clearDate

* Revert "feat: Handle clearDate"

This reverts commit fd145ef8e9589f3b4aa1a16e5d72c15b6d0b53c4.

* feat: Handle clearDate

* feat: Add clear_flag field

* feat: check clear_flag to clear date

* chore: update exisitng tests

* fix: add mssing import

* refactor: remove check for timeFormatError

* fix: fix margin on _ClearDateButton

* refactor: _ClearDateButton -> ClearDateButton

* test: add clear date backend unit test

* test: add clear date integration test

* test: add clear date to edit time cell test

* chore: mark ClearDateButton visible for testing

* feat: close date editor popover on clear date

* style: make AppFlowyPopover taller

* fix: trim dateStr in _dateStrFromCellData

* fix: don't dismiss CellEditor after clearDate
2023-07-31 10:23:20 +08:00
Richard ShiueandLucas.Xu ea37f46f88 fix: date field created when reference a calendar (#2910)
* fix: date field created when reference a calendar

* fix: make everything one transaction

* chore: apply suggestions from Lucas

* chore: do the same for board

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2023-07-31 10:12:26 +08:00
Nathan.fooo e68d3f1c71 tests: add supabase tests (#3084) 2023-07-30 08:39:01 +08:00
Jannes Blobel 5741feb7cc Add Transaltion section README.md (#2751)
* Add Transaltion section README.md

AppFlowy has a lot of missing translations. 
I thought you could use your community for the missing translation.

* Fix typo and add backticks for readability in README.md

* Update README.md
2023-07-29 15:00:25 +08:00
Nathan.foooandFu Zi Xiang 2cd88594e8 feat: migrate user data to cloud (#3078)
* refactor: weak passed-in params in handler

* refactor: rename struct

* chore: update tables

* chore: update schema

* chore: add permission

* chore: update tables

* chore: support transaction mode

* chore: workspace database id

* chore: add user workspace

* feat: return list of workspaces

* chore: add user to workspace

* feat: separate database row table

* refactor: update schema

* chore: partition table

* chore: use transaction

* refactor: dir

* refactor: collab db ref

* fix: collab db lock

* chore: rename files

* chore: add tables descriptions

* chore: update readme

* docs: update documentation

* chore: rename crate

* chore: update ref

* chore: update tests

* chore: update tests

* refactor: crate deps

* chore: update crate ref

* chore: remove unused deps

* chore: remove unused deps

* chore: update collab crate refs

* chore: replace client with transaction in pooler

* refactor: return error type

* refactor: use anyhow error in deps

* feat: supabase postgrest user signin (wip)

* fix: Cargo.toml source git deps, changed Error to anyhow::Error

* fix: uuid serialization

* chore: fix conflict

* chore: extend the response

* feat: add implementation place holders

* feat: impl get_user_workspaces

* feat: impl get_user_profile

* test: create workspace

* fix: postgrest: field names and alias

* chore: implement folder restful api

* chore: implement collab storate with restful api

* feat: added placeholders for impl: update_user_profile, check_user

* feat: impl: update_user_profile

* feat: impl: check_user

* fix: use UidResponse, add more debug info for serde serialization error

* fix: get_user_profile: use Optional<UserProfileResponse>

* chore: imple init sync

* chore: support soft delete

* feat: postgresql: add migration test

* feat: postgresql migration test: added UID display and colored output

* feat: postgresql migration test: workspace role

* feat: postgresql migration test: create shared common utils

* feat: postgresql migration test: fixed shebang

* chore: add flush_collab_update pg function

* chore: implement datbaase and document restful api

* chore: migrate to use restful api

* chore: update table schema

* chore: fix tests

* chore: remove unused code

* chore: format code

* chore: remove unused env

* fix: tauri build

* fix: tauri build

---------

Co-authored-by: Fu Zi Xiang <speed2exe@live.com.sg>
2023-07-29 09:46:24 +08:00
Kilu.He a885170869 feat: support editor format text color and bg color (#3061) 2023-07-27 20:40:18 +08:00
Kilu.He 915ce02157 fix: implement the interface of move nested views (#3042)
* fix: implement the interface of move nested views

* fix: update rust ci ubuntu version

* fix: update rust ci version
2023-07-26 16:49:50 +08:00
Lucas.Xu 6e9a5a16a6 fix: windows relesae job (#3038) 2023-07-25 16:41:03 +08:00
Richard Shiue 13c72a31b7 fix: cursor for block options on Windows (#3052) 2023-07-25 16:40:25 +08:00
Richard Shiue 96410b2d32 fix: text overflow when editing a filter 2023-07-25 09:07:21 +07:00
Lucas.Xu d32c2082b6 feat: support building on iOS platform (#3033) 2023-07-21 15:53:57 +08:00
Richard Shiue c368d855c7 feat: enable sort by date + sort fix (#2953) 2023-07-21 15:51:20 +08:00
Mayur Mahajan b1378b4545 feat: customize command shortcuts (#2848) 2023-07-20 16:11:00 +08:00
Kilu.He 5ab64f8835 feat: support views drag and drop (#3004) 2023-07-19 17:59:32 +08:00
490 changed files with 15251 additions and 6748 deletions

No files matched your search

+1
View File
@@ -134,6 +134,7 @@ jobs:
fail_ci_if_error: true
verbose: true
os: ${{ matrix.os }}
token: ${{ secrets.CODECOV_TOKEN }}
attempt_limit: 20
attempt_delay: 10000
+1
View File
@@ -136,5 +136,6 @@ jobs:
fail_ci_if_error: true
verbose: true
os: ${{ matrix.os }}
token: ${{ secrets.CODECOV_TOKEN }}
attempt_limit: 20
attempt_delay: 10000
+8
View File
@@ -20,6 +20,14 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
# - name: Create .env file
# working-directory: frontend/appflowy_flutter
# run: |
# touch .env
# echo SUPABASE_URL=${{ secrets.HOST_URL }} >> .env
# echo SUPABASE_ANON_KEY=${{ secrets.HOST_ANON_KEY }} >> .env
# echo SUPABASE_JWT_SECRET=${{ secrets.HOST_JWT_SECRET }} >> .env
- name: Build release notes
run: |
touch ${{ env.RELEASE_NOTES_PATH }}
+1 -27
View File
@@ -22,7 +22,6 @@ on:
env:
CARGO_TERM_COLOR: always
RUST_TOOLCHAIN: "1.70"
FLUTTER_VERSION: "3.10.1"
jobs:
test-on-ubuntu:
@@ -40,14 +39,6 @@ jobs:
components: rustfmt, clippy
profile: minimal
- name: Install flutter
id: flutter
uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version: ${{ env.FLUTTER_VERSION }}
cache: true
- name: Install prerequisites
working-directory: frontend
run: |
@@ -60,11 +51,6 @@ jobs:
workspaces: |
frontend/rust-lib
- name: Build FlowySDK
working-directory: frontend
run: |
cargo make --profile development-linux-x86_64 appflowy-core-dev
- name: Run rust-lib tests
working-directory: frontend/rust-lib
run: RUST_LOG=info RUST_BACKTRACE=1 cargo test --no-default-features --features="rev-sqlite"
@@ -75,16 +61,4 @@ jobs:
- name: clippy rust-lib
run: cargo clippy --features="rev-sqlite"
working-directory: frontend/rust-lib
- name: rustfmt shared-lib
run: cargo fmt --all -- --check
working-directory: shared-lib
- name: clippy shared-lib
run: cargo clippy -- -D warnings
working-directory: shared-lib
- name: Run shared-lib tests
working-directory: shared-lib
run: RUST_LOG=info cargo test --no-default-features
working-directory: frontend/rust-lib
+24 -2
View File
@@ -1,11 +1,33 @@
# Release Notes
## Version 0.2.8 - 08/03/2023
### New Features
- Nestable personal folder that supports drag and drop
- Support for favorite folders.
- Support for sorting by date in Grid view.
- Add a duplicate button in the Board context menu.
### Bug fixes
- Improve readability in Callout
- Some UI issues
## Version 0.2.7 - 07/18/2023
### New Features
- Dynamic load themes
- Inline math equation
<img width="1147" src="https://github.com/AppFlowy-IO/AppFlowy/assets/11863087/ac464740-c685-4a85-ae99-1074c1c607e5">
- Open page in new tab
- Create toggle lists to keep things tidy in your pages
- Alt/Option + click to add a text block above
### Bug fixes
- Pasting into a Grid property crashed on Windows
- Double-click a link to open
## Version 0.2.6 - 07/11/2023
+8
View File
@@ -68,6 +68,14 @@ Contributions make the open-source community a fantastic place to learn, inspire
If your Pull Request is accepted as it fixes a bug, adds functionality, or makes AppFlowy's codebase significantly easier to use or understand, **Congratulations!** If your administrative and managerial work behind the scenes sustains the community, **Congratulations!** You are now an official contributor to AppFlowy. Get in touch with us ([link](https://tally.so/r/mKP5z3)) to receive the very special Contributor T-shirt!
Proudly wear your T-shirt and show it to us by tagging [@appflowy](https://twitter.com/appflowy) on Twitter.
## Translations 🌎🗺
[![translation badge](https://inlang.com/badge?url=github.com/AppFlowy-IO/AppFlowy)](https://inlang.com/editor/github.com/AppFlowy-IO/AppFlowy?ref=badge)
To add translations, you can manually edit the JSON translation files in `/frontend/appflowy_flutter/assets/translations`, use the [inlang online editor](https://inlang.com/editor/github.com/AppFlowy-IO/AppFlowy), or run `npx inlang machine translate` to add missing translations.
## Join the community to build AppFlowy together
<a href="https://github.com/AppFlowy-IO/AppFlowy/graphs/contributors">
+22 -45
View File
@@ -43,47 +43,34 @@
},
"cwd": "${workspaceRoot}/appflowy_flutter"
},
{
"name": "AF-iOS: Clean + Rebuild All",
"request": "launch",
"program": "./lib/main.dart",
"type": "dart",
"preLaunchTask": "AF: Clean + Rebuild All (iOS)",
"env": {
"RUST_LOG": "trace"
},
"cwd": "${workspaceRoot}/appflowy_flutter"
},
{
"name": "AF-iOS-Simulator: Clean + Rebuild All",
"request": "launch",
"program": "./lib/main.dart",
"type": "dart",
"preLaunchTask": "AF: Clean + Rebuild All (iOS Simulator)",
"env": {
"RUST_LOG": "trace"
},
"cwd": "${workspaceRoot}/appflowy_flutter"
},
{
"name": "AF-desktop: Debug Rust",
"request": "attach",
"type": "lldb",
"pid": "${command:pickMyProcess}"
},
// {
// "name": "AF-desktop: profile mode",
// "request": "launch",
// "program": "./lib/main.dart",
// "type": "dart",
// "flutterMode": "profile",
// "cwd": "${workspaceRoot}/appflowy_flutter"
// },
{
// This task builds the Rust and Dart code of AppFlowy for android.
"name": "AF-android: Build All",
"request": "launch",
"program": "./lib/main.dart",
"type": "dart",
"preLaunchTask": "AF: build_mobile_sdk",
"env": {
"RUST_LOG": "info"
},
"cwd": "${workspaceRoot}/appflowy_flutter"
},
{
// This task builds will:
// - call the clean task,
// - rebuild all the generated Files (including freeze and language files)
// - rebuild the the Rust and Dart code of AppFlowy.
"name": "AF-android: Clean + Rebuild All",
"request": "launch",
"program": "./lib/main.dart",
"type": "dart",
"preLaunchTask": "AF: Clean + Rebuild All (Android)",
"env": {
"RUST_LOG": "info"
},
"cwd": "${workspaceRoot}/appflowy_flutter"
},
{
// https://tauri.app/v1/guides/debugging/vs-code
"type": "lldb",
@@ -99,15 +86,5 @@
"preLaunchTask": "AF: Tauri UI Dev",
"cwd": "${workspaceRoot}/appflowy_tauri/"
},
// {
// "type": "lldb",
// "request": "launch",
// "name": "AF-tauri: Production Debug",
// "cargo": {
// "args": ["build", "--release", "--manifest-path=./appflowy_tauri/src-tauri/Cargo.toml"]
// },
// "preLaunchTask": "AF: Tauri UI Build",
// "cwd": "${workspaceRoot}/appflowy_tauri/"
// },
]
}
+31 -4
View File
@@ -28,13 +28,13 @@
}
},
{
"label": "AF: Clean + Rebuild All (Android)",
"label": "AF: Clean + Rebuild All (iOS)",
"type": "shell",
"dependsOrder": "sequence",
"dependsOn": [
"AF: Dart Clean",
"AF: Flutter Clean",
"AF: Build Appflowy Core_for_android",
"AF: Build Appflowy Core For iOS",
"AF: Flutter Pub Get",
"AF: Flutter Package Get",
"AF: Generate Language Files",
@@ -46,9 +46,36 @@
}
},
{
"label": "AF: Build Appflowy Core_for_android",
"label": "AF: Clean + Rebuild All (iOS Simulator)",
"type": "shell",
"command": "cargo make --profile development-android appflowy-core-dev-android",
"dependsOrder": "sequence",
"dependsOn": [
"AF: Dart Clean",
"AF: Flutter Clean",
"AF: Build Appflowy Core For iOS Simulator",
"AF: Flutter Pub Get",
"AF: Flutter Package Get",
"AF: Generate Language Files",
"AF: Generate Freezed Files"
],
"presentation": {
"reveal": "always",
"panel": "new"
}
},
{
"label": "AF: Build Appflowy Core For iOS",
"type": "shell",
"command": "cargo make --profile development-ios-arm64 appflowy-core-dev-ios",
"group": "build",
"options": {
"cwd": "${workspaceFolder}"
}
},
{
"label": "AF: Build Appflowy Core For iOS Simulator",
"type": "shell",
"command": "cargo make --profile development-ios-arm64-sim appflowy-core-dev-ios",
"group": "build",
"options": {
"cwd": "${workspaceFolder}"
+18 -7
View File
@@ -2,6 +2,7 @@
extend = [
{ path = "scripts/makefile/desktop.toml" },
{ path = "scripts/makefile/mobile.toml" },
{ path = "scripts/makefile/protobuf.toml" },
{ path = "scripts/makefile/tests.toml" },
{ path = "scripts/makefile/docker.toml" },
@@ -23,7 +24,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
CARGO_MAKE_CRATE_NAME = "dart-ffi"
LIB_NAME = "dart_ffi"
CURRENT_APP_VERSION = "0.2.7"
CURRENT_APP_VERSION = "0.2.8"
FLUTTER_DESKTOP_FEATURES = "dart,rev-sqlite"
PRODUCT_NAME = "AppFlowy"
# CRATE_TYPE: https://doc.rust-lang.org/reference/linkage.html
@@ -150,6 +151,22 @@ LINUX_ARCH = "arm64"
APP_ENVIRONMENT = "production"
FLUTTER_DESKTOP_FEATURES = "dart,rev-sqlite,openssl_vendored"
[env.development-ios-arm64-sim]
BUILD_FLAG = "debug"
TARGET_OS = "ios"
FLUTTER_OUTPUT_DIR = "Debug"
RUST_COMPILE_TARGET = "aarch64-apple-ios-sim"
BUILD_ARCHS = "arm64"
CRATE_TYPE = "staticlib"
[env.development-ios-arm64]
BUILD_FLAG = "debug"
TARGET_OS = "ios"
FLUTTER_OUTPUT_DIR = "Debug"
RUST_COMPILE_TARGET = "aarch64-apple-ios"
BUILD_ARCHS = "arm64"
CRATE_TYPE = "staticlib"
[tasks.echo_env]
script = ['''
echo "-------- Env Parameters --------"
@@ -165,12 +182,6 @@ script = ['''
''']
script_runner = "@shell"
[env.production-ios]
BUILD_FLAG = "release"
TARGET_OS = "ios"
FLUTTER_OUTPUT_DIR = "Release"
PRODUCT_EXT = "ipa"
[env.development-android]
BUILD_FLAG = "debug"
TARGET_OS = "android"
@@ -1,3 +0,0 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 6L18.781 11.9243L25 12.8801L20.5 17.489L21.562 24L16 20.9243L10.438 24L11.5 17.489L7 12.8801L13.219 11.9243L16 6Z" fill="#FFD667" stroke="#FFD667" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 329 B

@@ -1,3 +0,0 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 6L18.781 11.9243L25 12.8801L20.5 17.489L21.562 24L16 20.9243L10.438 24L11.5 17.489L7 12.8801L13.219 11.9243L16 6Z" stroke="#333333" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 314 B

@@ -1,3 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 4.5L14.0858 8.94322L18.75 9.66009L15.375 13.1167L16.1715 18L12 15.6932L7.8285 18L8.625 13.1167L5.25 9.66009L9.91425 8.94322L12 4.5Z" stroke="#333333" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 3L9.3905 5.96215L12.5 6.44006L10.25 8.74448L10.781 12L8 10.4621L5.219 12L5.75 8.74448L3.5 6.44006L6.6095 5.96215L8 3Z" fill="#FFD667" stroke="#FFD667" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 334 B

After

Width:  |  Height:  |  Size: 315 B

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 3L9.3905 5.96215L12.5 6.44006L10.25 8.74448L10.781 12L8 10.4621L5.219 12L5.75 8.74448L3.5 6.44006L6.6095 5.96215L8 3Z" fill="#none" stroke="#FFFFFF" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 313 B

@@ -0,0 +1,50 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database_view/widgets/card/card.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../util/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('board row test', () {
testWidgets('delete item in ToDo card', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Board);
const name = 'Card 1';
final card1 = find.findTextInFlowyText(name);
await tester.hoverOnWidget(
card1,
onHover: () async {
final moreOption = find.byType(CardMoreOption);
await tester.tapButton(moreOption);
},
);
await tester.tapButtonWithName(LocaleKeys.button_delete.tr());
expect(find.findTextInFlowyText(name), findsNothing);
});
testWidgets('duplicate item in ToDo card', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Board);
const name = 'Card 1';
final card1 = find.findTextInFlowyText(name);
await tester.hoverOnWidget(
card1,
onHover: () async {
final moreOption = find.byType(CardMoreOption);
await tester.tapButton(moreOption);
},
);
await tester.tapButtonWithName(LocaleKeys.button_duplicate.tr());
expect(find.textContaining(name, findRichText: true), findsNWidgets(2));
});
});
}
@@ -0,0 +1,10 @@
import 'package:integration_test/integration_test.dart';
import 'board_row_test.dart' as board_row_test;
void startTesting() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
// Board integration tests
board_row_test.main();
}
@@ -14,8 +14,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateCalendarButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Calendar);
// open setting
await tester.tapDatabaseSettingButton();
@@ -36,7 +35,11 @@ void main() {
await tester.tapGoButton();
// Create calendar view
await tester.createNewPageWithName(ViewLayoutPB.Calendar, 'calendar');
const name = 'calendar';
await tester.createNewPageWithName(
name: name,
layout: ViewLayoutPB.Calendar,
);
// Open setting
await tester.tapDatabaseSettingButton();
@@ -47,9 +50,9 @@ void main() {
await tester.tapFirstDayOfWeekStartFromMonday();
// Open the other page and open the new calendar page again
await tester.openPage(readme);
await tester.openPage(gettingStated);
await tester.pumpAndSettle(const Duration(milliseconds: 300));
await tester.openPage('calendar');
await tester.openPage(name, layout: ViewLayoutPB.Calendar);
// Open setting again and check the start from Monday is selected
await tester.tapDatabaseSettingButton();
@@ -65,8 +68,7 @@ void main() {
await tester.tapGoButton();
// Create the calendar view
await tester.tapAddButton();
await tester.tapCreateCalendarButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Calendar);
// Scroll until today's date cell is visible
await tester.scrollToToday();
@@ -135,8 +137,7 @@ void main() {
await tester.tapGoButton();
// Create the calendar view
await tester.tapAddButton();
await tester.tapCreateCalendarButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Calendar);
// Create a new event on the first of this month
final today = DateTime.now();
@@ -15,8 +15,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
await tester.editCell(
rowIndex: 0,
@@ -38,7 +37,10 @@ void main() {
testWidgets('edit multiple text cells', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.createNewPageWithName(ViewLayoutPB.Grid, 'my grid');
await tester.createNewPageWithName(
name: 'my grid',
layout: ViewLayoutPB.Grid,
);
await tester.createField(FieldType.RichText, 'description');
await tester.editCell(
@@ -75,8 +77,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
const fieldType = FieldType.Number;
@@ -134,8 +135,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
await tester.assertCheckboxCell(rowIndex: 0, isSelected: false);
await tester.tapCheckboxCellInGrid(rowIndex: 0);
@@ -153,8 +153,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
const fieldType = FieldType.CreatedTime;
// Create a create time field
@@ -172,8 +171,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
const fieldType = FieldType.LastEditedTime;
// Create a last time field
@@ -191,8 +189,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
const fieldType = FieldType.DateTime;
await tester.createField(fieldType, fieldType.name);
@@ -268,6 +265,18 @@ void main() {
content: DateFormat('dd/MM/y hh:mm a').format(now),
);
await tester.tapCellInGrid(rowIndex: 0, fieldType: fieldType);
await tester.findDateEditor(findsOneWidget);
// Clear the date and time
await tester.clearDate();
await tester.assertDateCellInGrid(
rowIndex: 0,
fieldType: fieldType,
content: '',
);
await tester.pumpAndSettle();
});
@@ -276,9 +285,9 @@ void main() {
await tester.tapGoButton();
const fieldType = FieldType.SingleSelect;
await tester.tapAddButton();
// When create a grid, it will create a single select field by default
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Tap the cell to invoke the selection option editor
await tester.tapSelectOptionCellInGrid(rowIndex: 0, fieldType: fieldType);
@@ -354,8 +363,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
const fieldType = FieldType.MultiSelect;
await tester.createField(fieldType, fieldType.name);
@@ -17,8 +17,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Invoke the field editor
await tester.tapGridFieldWithName('Name');
@@ -35,8 +34,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Invoke the field editor
await tester.tapGridFieldWithName('Type');
@@ -58,8 +56,7 @@ void main() {
await tester.tapGoButton();
// create a new grid
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// create a field
await tester.createField(FieldType.Checklist, 'checklist');
@@ -73,8 +70,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// create a field
await tester.createField(FieldType.Checkbox, 'New field 1');
@@ -94,8 +90,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// create a field
await tester.scrollToRight(find.byType(GridPage));
@@ -115,8 +110,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// create a field
await tester.scrollToRight(find.byType(GridPage));
@@ -136,8 +130,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
await tester.scrollToRight(find.byType(GridPage));
await tester.tapNewPropertyButton();
@@ -157,8 +150,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
for (final fieldType in [
FieldType.Checklist,
@@ -190,7 +182,9 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.createNewPageWithName(ViewLayoutPB.Grid);
await tester.createNewPageWithName(
layout: ViewLayoutPB.Grid,
);
// Invoke the field editor
await tester.tapGridFieldWithName('Type');
@@ -9,7 +9,7 @@ import 'util/database_test_op.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('grid', () {
group('database filter', () {
testWidgets('add text filter', (tester) async {
await tester.openV020database();
@@ -1,5 +1,6 @@
import 'package:appflowy/plugins/database_view/widgets/row/row_banner.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart';
@@ -19,8 +20,7 @@ void main() {
await tester.tapGoButton();
// Create a new grid
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Hover first row and then open the row page
await tester.openFirstRowDetailPage();
@@ -34,8 +34,7 @@ void main() {
await tester.tapGoButton();
// Create a new grid
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Hover first row and then open the row page
await tester.openFirstRowDetailPage();
@@ -55,8 +54,7 @@ void main() {
await tester.tapGoButton();
// Create a new grid
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Hover first row and then open the row page
await tester.openFirstRowDetailPage();
@@ -85,8 +83,7 @@ void main() {
await tester.tapGoButton();
// Create a new grid
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Hover first row and then open the row page
await tester.openFirstRowDetailPage();
@@ -108,8 +105,7 @@ void main() {
await tester.tapGoButton();
// Create a new grid
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Hover first row and then open the row page
await tester.openFirstRowDetailPage();
@@ -144,8 +140,7 @@ void main() {
await tester.tapGoButton();
// Create a new grid
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Hover first row and then open the row page
await tester.openFirstRowDetailPage();
@@ -160,8 +155,7 @@ void main() {
await tester.tapGoButton();
// Create a new grid
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Hover first row and then open the row page
await tester.openFirstRowDetailPage();
@@ -201,8 +195,7 @@ void main() {
await tester.tapGoButton();
// Create a new grid
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Hover first row and then open the row page
await tester.openFirstRowDetailPage();
@@ -241,8 +234,7 @@ void main() {
await tester.tapGoButton();
// Create a new grid
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Hover first row and then open the row page
await tester.openFirstRowDetailPage();
@@ -258,8 +250,7 @@ void main() {
await tester.tapGoButton();
// Create a new grid
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Hover first row and then open the row page
await tester.openFirstRowDetailPage();
@@ -1,3 +1,4 @@
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
@@ -12,8 +13,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
await tester.tapCreateRowButtonInGrid();
// The initial number of rows is 3
@@ -25,8 +25,8 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
await tester.hoverOnFirstRowOfGrid();
await tester.tapCreateRowButtonInRowMenuOfGrid();
@@ -41,8 +41,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
await tester.hoverOnFirstRowOfGrid();
// Open the row menu and then click the delete
@@ -60,8 +59,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
await tester.assertRowCountInGridPage(3);
await tester.pumpAndSettle();
@@ -1,4 +1,5 @@
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
@@ -13,8 +14,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// open setting
await tester.tapDatabaseSettingButton();
@@ -31,8 +31,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// open setting
await tester.tapDatabaseSettingButton();
@@ -10,22 +10,22 @@ void main() {
group('grid', () {
testWidgets('add text sort', (tester) async {
await tester.openV020database();
// create a filter
// create a sort
await tester.tapDatabaseSortButton();
await tester.tapCreateSortByFieldType(FieldType.RichText, 'Name');
// check the text cell order
final textCells = <String>[
'',
'',
'',
'',
'',
'A',
'B',
'C',
'D',
'E',
'',
'',
'',
'',
'',
];
for (final (index, content) in textCells.indexed) {
await tester.assertCellContent(
@@ -142,7 +142,6 @@ void main() {
// check the number cell order
for (final (index, content) in <String>[
'',
'-2',
'-1',
'0.1',
@@ -152,6 +151,7 @@ void main() {
'10',
'11',
'12',
'',
].indexed) {
await tester.assertCellContent(
rowIndex: index,
@@ -186,53 +186,74 @@ void main() {
await tester.pumpAndSettle();
});
testWidgets('add number and text sort', (tester) async {
testWidgets('add checkbox and number sort', (tester) async {
await tester.openV020database();
// create a filter
await tester.tapDatabaseSortButton();
await tester.tapCreateSortByFieldType(FieldType.Number, 'number');
await tester.tapCreateSortByFieldType(FieldType.Checkbox, 'Done');
// open the sort menu and select number order by descending
// open the sort menu and sort checkbox by descending
await tester.tapSortMenuInSettingBar();
await tester.tapSortButtonByName('number');
await tester.tapSortButtonByName('Done');
await tester.tapSortByDescending();
for (final (index, content) in <String>[
'12',
'11',
'10',
'2',
'1',
'0.2',
'0.1',
'-1',
'-2',
'',
for (final (index, content) in <bool>[
true,
true,
true,
true,
true,
false,
false,
false,
false,
false,
].indexed) {
await tester.assertCellContent(
await tester.assertCheckboxCell(
rowIndex: index,
fieldType: FieldType.Number,
content: content,
isSelected: content,
);
}
// add another sort, this time by number descending
await tester.tapSortMenuInSettingBar();
await tester.tapCreateSortByFieldTypeInSortMenu(
FieldType.RichText,
'Name',
FieldType.Number,
'number',
);
await tester.tapSortButtonByName('number');
await tester.tapSortByDescending();
// check checkbox cell order
for (final (index, content) in <bool>[
true,
true,
true,
true,
true,
false,
false,
false,
false,
false,
].indexed) {
await tester.assertCheckboxCell(
rowIndex: index,
isSelected: content,
);
}
// check number cell order
for (final (index, content) in <String>[
'1',
'0.2',
'0.1',
'-1',
'-2',
'12',
'11',
'10',
'2',
'',
'-1',
'-2',
'0.1',
'0.2',
'1',
].indexed) {
await tester.assertCellContent(
rowIndex: index,
@@ -241,26 +262,6 @@ void main() {
);
}
// check text cell order
for (final (index, content) in <String>[
'',
'',
'',
'',
'',
'A',
'B',
'C',
'D',
'E',
].indexed) {
await tester.assertCellContent(
rowIndex: index,
fieldType: FieldType.RichText,
content: content,
);
}
await tester.pumpAndSettle();
});
});
@@ -15,8 +15,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Create board view
await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.board);
@@ -37,8 +36,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Create board view
await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.board);
@@ -63,8 +61,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Create board view
await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.board);
@@ -17,9 +17,7 @@ void main() {
await tester.tapGoButton();
// create a new document
await tester.tapAddButton();
await tester.tapCreateDocumentButton();
await tester.pumpAndSettle();
await tester.createNewPageWithName();
// expect to see a new document
tester.expectToSeePageName(
@@ -35,19 +33,21 @@ void main() {
await tester.tapGoButton();
// delete the readme page
await tester.hoverOnPageName(readme);
await tester.tapDeletePageButton();
await tester.hoverOnPageName(
gettingStated,
onHover: () async => await tester.tapDeletePageButton(),
);
// the banner should show up and the readme page should be gone
tester.expectToSeeDocumentBanner();
tester.expectNotToSeePageName(readme);
tester.expectNotToSeePageName(gettingStated);
// restore the readme page
await tester.tapRestoreButton();
// the banner should be gone and the readme page should be back
tester.expectNotToSeeDocumentBanner();
tester.expectToSeePageName(readme);
tester.expectToSeePageName(gettingStated);
});
testWidgets('delete the readme page and delete it permanently',
@@ -57,19 +57,21 @@ void main() {
await tester.tapGoButton();
// delete the readme page
await tester.hoverOnPageName(readme);
await tester.tapDeletePageButton();
await tester.hoverOnPageName(
gettingStated,
onHover: () async => await tester.tapDeletePageButton(),
);
// the banner should show up and the readme page should be gone
tester.expectToSeeDocumentBanner();
tester.expectNotToSeePageName(readme);
tester.expectNotToSeePageName(gettingStated);
// delete the page permanently
await tester.tapDeletePermanentlyButton();
// the banner should be gone and the readme page should be gone
tester.expectNotToSeeDocumentBanner();
tester.expectNotToSeePageName(readme);
tester.expectNotToSeePageName(gettingStated);
});
});
}
@@ -1,9 +1,12 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database_view/board/presentation/board_page.dart';
import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_page.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/link_to_page_widget.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
@@ -61,6 +64,54 @@ void main() {
findsOneWidget,
);
});
testWidgets('create a grid inside a document', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await createInlineDatabase(tester, ViewLayoutPB.Grid);
// validate the referenced grid is inserted
expect(
find.descendant(
of: find.byType(AppFlowyEditor),
matching: find.byType(GridPage),
),
findsOneWidget,
);
});
testWidgets('create a board inside a document', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await createInlineDatabase(tester, ViewLayoutPB.Board);
// validate the referenced grid is inserted
expect(
find.descendant(
of: find.byType(AppFlowyEditor),
matching: find.byType(BoardPage),
),
findsOneWidget,
);
});
testWidgets('create a calendar inside a document', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await createInlineDatabase(tester, ViewLayoutPB.Calendar);
// validate the referenced grid is inserted
expect(
find.descendant(
of: find.byType(AppFlowyEditor),
matching: find.byType(CalendarPage),
),
findsOneWidget,
);
});
});
}
@@ -73,13 +124,15 @@ Future<void> insertReferenceDatabase(
final id = uuid();
final name = '${layout.name}_$id';
await tester.createNewPageWithName(
layout,
name,
name: name,
layout: layout,
openAfterCreated: false,
);
// create a new document
await tester.createNewPageWithName(
ViewLayoutPB.Document,
'insert_a_reference_${layout.name}',
name: 'insert_a_reference_${layout.name}',
layout: ViewLayoutPB.Document,
openAfterCreated: true,
);
// tap the first line of the document
await tester.editor.tapLineOfEditorAt(0);
@@ -98,3 +151,38 @@ Future<void> insertReferenceDatabase(
expect(referencedDatabase, findsOneWidget);
await tester.tapButton(referencedDatabase);
}
Future<void> createInlineDatabase(
WidgetTester tester,
ViewLayoutPB layout,
) async {
// create a new document
final documentName = 'insert_a_inline_${layout.name}';
await tester.createNewPageWithName(
name: documentName,
layout: ViewLayoutPB.Document,
openAfterCreated: true,
);
// tap the first line of the document
await tester.editor.tapLineOfEditorAt(0);
// insert a referenced view
await tester.editor.showSlashMenu();
final name = switch (layout) {
ViewLayoutPB.Grid => LocaleKeys.document_slashMenu_grid_createANewGrid.tr(),
ViewLayoutPB.Board =>
LocaleKeys.document_slashMenu_board_createANewBoard.tr(),
ViewLayoutPB.Calendar =>
LocaleKeys.document_slashMenu_calendar_createANewCalendar.tr(),
_ => '',
};
await tester.editor.tapSlashMenuItemWithName(
name,
);
await tester.pumpAndSettle();
final childViews = tester
.widget<SingleInnerViewItem>(tester.findPageName(documentName))
.view
.childViews;
expect(childViews.length, 1);
}
@@ -21,8 +21,8 @@ void main() {
// create a new document
await tester.createNewPageWithName(
ViewLayoutPB.Document,
LocaleKeys.document_plugins_createInlineMathEquation.tr(),
name: LocaleKeys.document_plugins_createInlineMathEquation.tr(),
layout: ViewLayoutPB.Document,
);
// tap the first line of the document
@@ -67,8 +67,8 @@ void main() {
// create a new document
await tester.createNewPageWithName(
ViewLayoutPB.Document,
LocaleKeys.document_plugins_createInlineMathEquation.tr(),
name: LocaleKeys.document_plugins_createInlineMathEquation.tr(),
layout: ViewLayoutPB.Document,
);
// tap the first line of the document
@@ -62,9 +62,11 @@ void main() {
final pageName = await insertingInlinePage(tester, ViewLayoutPB.Document);
// rename
await tester.hoverOnPageName(pageName);
const newName = 'RenameToNewPageName';
await tester.renamePage(newName);
await tester.hoverOnPageName(
pageName,
onHover: () async => await tester.renamePage(newName),
);
final finder = find.descendant(
of: find.byType(MentionPageBlock),
matching: find.findTextInFlowyText(newName),
@@ -79,8 +81,11 @@ void main() {
final pageName = await insertingInlinePage(tester, ViewLayoutPB.Grid);
// rename
await tester.hoverOnPageName(pageName);
await tester.tapDeletePageButton();
await tester.hoverOnPageName(
pageName,
layout: ViewLayoutPB.Grid,
onHover: () async => await tester.tapDeletePageButton(),
);
final finder = find.descendant(
of: find.byType(MentionPageBlock),
matching: find.findTextInFlowyText(pageName),
@@ -101,13 +106,14 @@ Future<String> insertingInlinePage(
final id = uuid();
final name = '${layout.name}_$id';
await tester.createNewPageWithName(
layout,
name,
name: name,
layout: layout,
openAfterCreated: false,
);
// create a new document
await tester.createNewPageWithName(
ViewLayoutPB.Document,
'insert_a_inline_page_${layout.name}',
name: 'insert_a_inline_page_${layout.name}',
layout: ViewLayoutPB.Document,
);
// tap the first line of the document
await tester.editor.tapLineOfEditorAt(0);
@@ -25,7 +25,7 @@ void main() {
// create a new document
await tester.createNewPageWithName(
ViewLayoutPB.Document,
layout: ViewLayoutPB.Document,
);
// tap the first line of the document
@@ -16,8 +16,8 @@ void main() {
await tester.tapGoButton();
await tester.createNewPageWithName(
ViewLayoutPB.Document,
'outline_test',
name: 'outline_test',
layout: ViewLayoutPB.Document,
);
await tester.editor.tapLineOfEditorAt(0);
@@ -33,8 +33,8 @@ void main() {
await tester.tapGoButton();
await tester.createNewPageWithName(
ViewLayoutPB.Document,
'outline_test',
name: 'outline_test',
layout: ViewLayoutPB.Document,
);
await tester.editor.tapLineOfEditorAt(0);
@@ -32,7 +32,7 @@ void main() {
// create a new document
await tester.createNewPageWithName(
ViewLayoutPB.Document,
layout: ViewLayoutPB.Document,
);
// tap the first line of the document
@@ -78,7 +78,7 @@ void main() {
// create a new document
await tester.createNewPageWithName(
ViewLayoutPB.Document,
layout: ViewLayoutPB.Document,
);
// tap the first line of the document
@@ -118,7 +118,7 @@ void main() {
// create a new document
await tester.createNewPageWithName(
ViewLayoutPB.Document,
layout: ViewLayoutPB.Document,
);
// tap the first line of the document
@@ -156,7 +156,7 @@ void main() {
// create a new document
await tester.createNewPageWithName(
ViewLayoutPB.Document,
layout: ViewLayoutPB.Document,
);
// tap the first line of the document
@@ -191,7 +191,7 @@ void main() {
// create a new document
await tester.createNewPageWithName(
ViewLayoutPB.Document,
layout: ViewLayoutPB.Document,
);
// tap the first line of the document
@@ -18,7 +18,10 @@ void main() {
// create a new document called Sample
const pageName = 'Sample';
await tester.createNewPageWithName(ViewLayoutPB.Document, pageName);
await tester.createNewPageWithName(
name: pageName,
layout: ViewLayoutPB.Document,
);
// focus on the editor
await tester.editor.tapLineOfEditorAt(0);
@@ -56,7 +59,7 @@ void main() {
);
// switch to other page and switch back
await tester.openPage(readme);
await tester.openPage(gettingStated);
await tester.openPage(pageName);
// the numbered list should be kept
@@ -72,7 +75,10 @@ void main() {
// create a new document called Sample
const pageName = 'Sample';
await tester.createNewPageWithName(ViewLayoutPB.Document, pageName);
await tester.createNewPageWithName(
name: pageName,
layout: ViewLayoutPB.Document,
);
// focus on the editor
await tester.editor.tapLineOfEditorAt(0);
@@ -85,7 +91,7 @@ void main() {
}
// switch to other page and switch back
await tester.openPage(readme);
await tester.openPage(gettingStated);
await tester.openPage(pageName);
// this screenshots are different on different platform, so comment it out temporarily.
@@ -16,10 +16,10 @@ void main() {
final context = await tester.initializeAppFlowy();
await tester.tapGoButton();
// expect to see a readme page
tester.expectToSeePageName(readme);
// expect to see a getting started page
tester.expectToSeePageName(gettingStated);
await tester.tapAddButton();
await tester.tapAddViewButton();
await tester.tapImportButton();
final testFileNames = ['test1.md', 'test2.md'];
@@ -14,6 +14,8 @@ import 'document/document_test_runner.dart' as document_test_runner;
import 'import_files_test.dart' as import_files_test;
import 'share_markdown_test.dart' as share_markdown_test;
import 'switch_folder_test.dart' as switch_folder_test;
import 'sidebar/sidebar_test_runner.dart' as sidebar_test_runner;
import 'board/board_test_runner.dart' as board_test_runner;
/// The main task runner for all integration tests in AppFlowy.
///
@@ -31,6 +33,12 @@ void main() {
// Document integration tests
document_test_runner.startTesting();
// Sidebar integration tests
sidebar_test_runner.startTesting();
// Board integration test
board_test_runner.startTesting();
// Database integration tests
database_cell_test.main();
database_field_test.main();
@@ -16,7 +16,7 @@ void main() {
await tester.tapGoButton();
// expect to see a readme page
tester.expectToSeePageName(readme);
tester.expectToSeePageName(gettingStated);
// mock the file picker
final path = await mockSaveFilePath(
@@ -42,12 +42,16 @@ void main() {
final context = await tester.initializeAppFlowy();
await tester.tapGoButton();
// expect to see a readme page
tester.expectToSeePageName(readme);
// expect to see a getting started page
tester.expectToSeePageName(gettingStated);
// rename the document
await tester.hoverOnPageName(readme);
await tester.renamePage('example');
await tester.hoverOnPageName(
gettingStated,
onHover: () async {
await tester.renamePage('example');
},
);
final shareButton = find.byType(ShareActionList);
final shareButtonState =
@@ -0,0 +1,48 @@
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/folder/personal_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';
import 'package:integration_test/integration_test.dart';
import '../util/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('sidebar expand test', () {
bool isExpanded({required FolderCategoryType type}) {
if (type == FolderCategoryType.personal) {
return find
.descendant(
of: find.byType(PersonalFolder),
matching: find.byType(ViewItem),
)
.evaluate()
.isNotEmpty;
}
return false;
}
testWidgets('first time the personal folder is expanded', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
// first time is expanded
expect(isExpanded(type: FolderCategoryType.personal), true);
// collapse the personal folder
await tester.tapButton(
find.byTooltip(LocaleKeys.sideBar_clickToHidePersonal.tr()),
);
expect(isExpanded(type: FolderCategoryType.personal), false);
// expand the personal folder
await tester.tapButton(
find.byTooltip(LocaleKeys.sideBar_clickToHidePersonal.tr()),
);
expect(isExpanded(type: FolderCategoryType.personal), true);
});
});
}
@@ -0,0 +1,175 @@
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/folder/favorite_folder.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../util/base.dart';
import '../util/common_operations.dart';
import '../util/expectation.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('Favorites', () {
testWidgets(
'Toggle favorites for views creates / removes the favorite header along with favorite views',
(tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
// no favorite folder
expect(find.byType(FavoriteFolder), findsNothing);
// create the nested views
final names = [
1,
2,
].map((e) => 'document_$e').toList();
for (var i = 0; i < names.length; i++) {
final parentName = i == 0 ? gettingStated : names[i - 1];
await tester.createNewPageWithName(
name: names[i],
parentName: parentName,
layout: ViewLayoutPB.Document,
);
tester.expectToSeePageName(
names[i],
parentName: parentName,
layout: ViewLayoutPB.Document,
parentLayout: ViewLayoutPB.Document,
);
}
await tester.favoriteViewByName(gettingStated);
expect(
tester.findFavoritePageName(gettingStated),
findsOneWidget,
);
await tester.favoriteViewByName(names[1]);
expect(
tester.findFavoritePageName(names[1]),
findsNWidgets(2),
);
await tester.unfavoriteViewByName(gettingStated);
expect(
tester.findFavoritePageName(gettingStated),
findsNothing,
);
expect(
tester.findFavoritePageName(
names[1],
),
findsOneWidget,
);
await tester.unfavoriteViewByName(names[1]);
expect(
tester.findFavoritePageName(
names[1],
),
findsNothing,
);
});
testWidgets(
'renaming a favorite view updates name under favorite header',
(tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
const name = 'test';
await tester.favoriteViewByName(gettingStated);
await tester.hoverOnPageName(
gettingStated,
layout: ViewLayoutPB.Document,
onHover: () async {
await tester.renamePage(name);
await tester.pumpAndSettle();
},
);
expect(
tester.findPageName(name),
findsNWidgets(2),
);
expect(
tester.findFavoritePageName(name),
findsNothing,
);
},
);
testWidgets(
'deleting first level favorite view removes its instance from favorite header, deleting root level views leads to removal of all favorites that are its children',
(tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
final names = [1, 2].map((e) => 'document_$e').toList();
for (var i = 0; i < names.length; i++) {
final parentName = i == 0 ? gettingStated : names[i - 1];
await tester.createNewPageWithName(
name: names[i],
parentName: parentName,
layout: ViewLayoutPB.Document,
);
tester.expectToSeePageName(names[i], parentName: parentName);
}
await tester.favoriteViewByName(gettingStated);
await tester.favoriteViewByName(names[0]);
await tester.favoriteViewByName(names[1]);
expect(
find.byWidgetPredicate(
(widget) =>
widget is ViewItem &&
widget.view.isFavorite &&
widget.categoryType == FolderCategoryType.favorite,
),
findsNWidgets(6),
);
await tester.hoverOnPageName(
names[1],
layout: ViewLayoutPB.Document,
onHover: () async {
await tester.tapDeletePageButton();
await tester.pumpAndSettle();
},
);
expect(
find.byWidgetPredicate(
(widget) =>
widget is ViewItem &&
widget.view.isFavorite &&
widget.categoryType == FolderCategoryType.favorite,
),
findsNWidgets(3),
);
await tester.hoverOnPageName(
gettingStated,
layout: ViewLayoutPB.Document,
onHover: () async {
await tester.tapDeletePageButton();
await tester.pumpAndSettle();
},
);
expect(
find.byWidgetPredicate(
(widget) =>
widget is ViewItem &&
widget.view.isFavorite &&
widget.categoryType == FolderCategoryType.favorite,
),
findsNothing,
);
},
);
});
}
@@ -0,0 +1,209 @@
import 'package:appflowy/plugins/database_view/board/presentation/board_page.dart';
import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_page.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/draggable_view_item.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_add_button.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_more_action_button.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../util/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('sidebar test', () {
testWidgets('create a new page', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
// create a new page
const name = 'Hello AppFlowy';
await tester.tapNewPageButton();
await tester.enterText(find.byType(TextFormField), name);
await tester.tapOKButton();
// expect to see a new document
tester.expectToSeePageName(
name,
);
// and with one paragraph block
expect(find.byType(TextBlockComponentWidget), findsOneWidget);
});
testWidgets('create a new document, grid, board and calendar',
(tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
for (final layout in ViewLayoutPB.values) {
// create a new page
final name = 'AppFlowy_$layout';
await tester.createNewPageWithName(
name: name,
layout: layout,
);
// expect to see a new page
tester.expectToSeePageName(
name,
layout: layout,
);
switch (layout) {
case ViewLayoutPB.Document:
// and with one paragraph block
expect(find.byType(TextBlockComponentWidget), findsOneWidget);
break;
case ViewLayoutPB.Grid:
expect(find.byType(GridPage), findsOneWidget);
break;
case ViewLayoutPB.Board:
expect(find.byType(BoardPage), findsOneWidget);
break;
case ViewLayoutPB.Calendar:
expect(find.byType(CalendarPage), findsOneWidget);
break;
}
await tester.openPage(gettingStated);
}
});
testWidgets('create some nested pages, and move them', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
final names = [1, 2, 3, 4].map((e) => 'document_$e').toList();
for (var i = 0; i < names.length; i++) {
final parentName = i == 0 ? gettingStated : names[i - 1];
await tester.createNewPageWithName(
name: names[i],
parentName: parentName,
layout: ViewLayoutPB.Document,
);
tester.expectToSeePageName(names[i], parentName: parentName);
}
// move the document_3 to the getting started page
await tester.movePageToOtherPage(
name: names[3],
parentName: gettingStated,
layout: ViewLayoutPB.Document,
parentLayout: ViewLayoutPB.Document,
);
final fromId = tester
.widget<SingleInnerViewItem>(tester.findPageName(names[3]))
.view
.parentViewId;
final toId = tester
.widget<SingleInnerViewItem>(tester.findPageName(gettingStated))
.view
.id;
expect(fromId, toId);
// move the document_2 before document_1
await tester.movePageToOtherPage(
name: names[2],
parentName: gettingStated,
layout: ViewLayoutPB.Document,
parentLayout: ViewLayoutPB.Document,
position: DraggableHoverPosition.bottom,
);
final childViews = tester
.widget<SingleInnerViewItem>(tester.findPageName(gettingStated))
.view
.childViews;
expect(
childViews[0].id,
tester
.widget<SingleInnerViewItem>(tester.findPageName(names[2]))
.view
.id,
);
expect(
childViews[1].id,
tester
.widget<SingleInnerViewItem>(tester.findPageName(names[0]))
.view
.id,
);
expect(
childViews[2].id,
tester
.widget<SingleInnerViewItem>(tester.findPageName(names[3]))
.view
.id,
);
});
testWidgets('unable to move a document into a database', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
const document = 'document';
await tester.createNewPageWithName(
name: document,
openAfterCreated: false,
);
tester.expectToSeePageName(document, layout: ViewLayoutPB.Document);
const grid = 'grid';
await tester.createNewPageWithName(
name: grid,
layout: ViewLayoutPB.Grid,
openAfterCreated: false,
);
tester.expectToSeePageName(grid, layout: ViewLayoutPB.Grid);
// move the document to the grid page
await tester.movePageToOtherPage(
name: document,
parentName: grid,
layout: ViewLayoutPB.Document,
parentLayout: ViewLayoutPB.Grid,
);
// it should not be moved
final childViews = tester
.widget<SingleInnerViewItem>(tester.findPageName(gettingStated))
.view
.childViews;
expect(
childViews[0].name,
document,
);
expect(
childViews[1].name,
grid,
);
});
testWidgets('unable to create a new database inside the existing one',
(tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
const grid = 'grid';
await tester.createNewPageWithName(
name: grid,
layout: ViewLayoutPB.Grid,
openAfterCreated: true,
);
tester.expectToSeePageName(grid, layout: ViewLayoutPB.Grid);
await tester.hoverOnPageName(
grid,
layout: ViewLayoutPB.Grid,
onHover: () async {
expect(find.byType(ViewAddButton), findsNothing);
expect(find.byType(ViewMoreActionButton), findsOneWidget);
},
);
});
});
}
@@ -0,0 +1,14 @@
import 'package:integration_test/integration_test.dart';
import 'sidebar_test.dart' as sidebar_test;
import 'sidebar_expand_test.dart' as sidebar_expanded_test;
import 'sidebar_favorites_test.dart' as sidebar_favorite_test;
void startTesting() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
// Sidebar integration tests
sidebar_test.main();
sidebar_expanded_test.main();
sidebar_favorite_test.main();
}
@@ -29,8 +29,14 @@ void main() {
findsNothing,
);
await tester.createNewPageWithName(ViewLayoutPB.Calendar, _calendarName);
await tester.createNewPageWithName(ViewLayoutPB.Document, _documentName);
await tester.createNewPageWithName(
name: _calendarName,
layout: ViewLayoutPB.Calendar,
);
await tester.createNewPageWithName(
name: _documentName,
layout: ViewLayoutPB.Document,
);
// Navigate current view to "Read me" document again
await tester.tapButtonWithName(_readmeName);
@@ -91,7 +91,6 @@ extension AppFlowyTestBase on WidgetTester {
warnIfMissed: warnIfMissed,
);
await pumpAndSettle(Duration(milliseconds: milliseconds));
return;
}
Future<void> tapButtonWithName(
@@ -1,15 +1,19 @@
import 'dart:ui';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_new_page_button.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';
import 'package:appflowy/workspace/presentation/home/menu/view/view_more_action_button.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/share/share_button.dart';
import 'package:appflowy/user/presentation/skip_log_in_screen.dart';
import 'package:appflowy/workspace/presentation/home/menu/app/header/add_button.dart';
import 'package:appflowy/workspace/presentation/home/menu/app/section/item.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_language_view.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/widget/buttons/primary_button.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
@@ -24,35 +28,48 @@ extension CommonOperations on WidgetTester {
}
/// Tap the + button on the home page.
Future<void> tapAddButton() async {
final addButton = find.byType(AddButton);
await tapButton(addButton);
Future<void> tapAddViewButton({
String name = gettingStated,
}) async {
await hoverOnPageName(
name,
onHover: () async {
final addButton = find.byType(ViewAddButton);
await tapButton(addButton);
},
);
}
/// Tap the 'New Page' Button on the sidebar.
Future<void> tapNewPageButton() async {
final newPageButton = find.byType(SidebarNewPageButton);
await tapButton(newPageButton);
}
/// Tap the create document button.
///
/// Must call [tapAddButton] first.
/// Must call [tapAddViewButton] first.
Future<void> tapCreateDocumentButton() async {
await tapButtonWithName(LocaleKeys.document_menuName.tr());
}
/// Tap the create grid button.
///
/// Must call [tapAddButton] first.
/// Must call [tapAddViewButton] first.
Future<void> tapCreateGridButton() async {
await tapButtonWithName(LocaleKeys.grid_menuName.tr());
}
/// Tap the create grid button.
///
/// Must call [tapAddButton] first.
/// Must call [tapAddViewButton] first.
Future<void> tapCreateCalendarButton() async {
await tapButtonWithName(LocaleKeys.calendar_menuName.tr());
}
/// Tap the import button.
///
/// Must call [tapAddButton] first.
/// Must call [tapAddViewButton] first.
Future<void> tapImportButton() async {
await tapButtonWithName(LocaleKeys.moreAction_import.tr());
}
@@ -116,6 +133,7 @@ extension CommonOperations on WidgetTester {
Finder finder, {
Offset? offset,
Future<void> Function()? onHover,
bool removePointer = true,
}) async {
try {
final gesture = await createGesture(kind: PointerDeviceKind.mouse);
@@ -133,19 +151,30 @@ extension CommonOperations on WidgetTester {
/// Hover on the page name.
Future<void> hoverOnPageName(
String name, {
ViewLayoutPB layout = ViewLayoutPB.Document,
Future<void> Function()? onHover,
bool useLast = true,
}) async {
final pageNames = findPageName(name, layout: layout);
if (useLast) {
await hoverOnWidget(findPageName(name).last, onHover: onHover);
await hoverOnWidget(
pageNames.last,
onHover: onHover,
);
} else {
await hoverOnWidget(findPageName(name).first, onHover: onHover);
await hoverOnWidget(
pageNames.first,
onHover: onHover,
);
}
}
/// open the page with given name.
Future<void> openPage(String name) async {
final finder = findPageName(name);
Future<void> openPage(
String name, {
ViewLayoutPB layout = ViewLayoutPB.Document,
}) async {
final finder = findPageName(name, layout: layout);
expect(finder, findsOneWidget);
await tapButton(finder);
}
@@ -154,20 +183,32 @@ extension CommonOperations on WidgetTester {
///
/// Must call [hoverOnPageName] first.
Future<void> tapPageOptionButton() async {
final optionButton = find.byType(ViewDisclosureButton);
final optionButton = find.byType(ViewMoreActionButton);
await tapButton(optionButton);
}
/// Tap the delete page button.
Future<void> tapDeletePageButton() async {
await tapPageOptionButton();
await tapButtonWithName(ViewDisclosureAction.delete.name);
await tapButtonWithName(ViewMoreActionType.delete.name);
}
/// Tap the rename page button.
Future<void> tapRenamePageButton() async {
await tapPageOptionButton();
await tapButtonWithName(ViewDisclosureAction.rename.name);
await tapButtonWithName(ViewMoreActionType.rename.name);
}
/// Tap the favorite page button
Future<void> tapFavoritePageButton() async {
await tapPageOptionButton();
await tapButtonWithName(ViewMoreActionType.favorite.name);
}
/// Tap the unfavorite page button
Future<void> tapUnfavoritePageButton() async {
await tapPageOptionButton();
await tapButtonWithName(ViewMoreActionType.unFavorite.name);
}
/// Rename the page.
@@ -224,12 +265,14 @@ extension CommonOperations on WidgetTester {
await tapButton(markdownButton);
}
Future<void> createNewPageWithName(
ViewLayoutPB layout, [
Future<void> createNewPageWithName({
String? name,
]) async {
ViewLayoutPB layout = ViewLayoutPB.Document,
String? parentName,
bool openAfterCreated = true,
}) async {
// create a new page
await tapAddButton();
await tapAddViewButton(name: parentName ?? gettingStated);
await tapButtonWithName(layout.menuName);
await pumpAndSettle();
@@ -237,6 +280,7 @@ extension CommonOperations on WidgetTester {
if (name != null) {
await hoverOnPageName(
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
layout: layout,
onHover: () async {
await renamePage(name);
await pumpAndSettle();
@@ -244,6 +288,16 @@ extension CommonOperations on WidgetTester {
);
await pumpAndSettle();
}
// open the page after created
if (openAfterCreated) {
await openPage(
// if the name is null, use the default name
name ?? LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
layout: layout,
);
await pumpAndSettle();
}
}
Future<void> simulateKeyEvent(
@@ -289,6 +343,64 @@ extension CommonOperations on WidgetTester {
await tap(find.text(LocaleKeys.disclosureAction_openNewTab.tr()));
await pumpAndSettle();
}
Future<void> favoriteViewByName(
String name, {
ViewLayoutPB layout = ViewLayoutPB.Document,
}) async {
await hoverOnPageName(
name,
layout: layout,
useLast: true,
onHover: () async {
await tapFavoritePageButton();
await pumpAndSettle();
},
);
}
Future<void> unfavoriteViewByName(
String name, {
ViewLayoutPB layout = ViewLayoutPB.Document,
}) async {
await hoverOnPageName(
name,
layout: layout,
useLast: true,
onHover: () async {
await tapUnfavoritePageButton();
await pumpAndSettle();
},
);
}
Future<void> movePageToOtherPage({
required String name,
required String parentName,
required ViewLayoutPB layout,
required ViewLayoutPB parentLayout,
DraggableHoverPosition position = DraggableHoverPosition.center,
}) async {
final from = findPageName(name, layout: layout);
final to = findPageName(parentName, layout: parentLayout);
final gesture = await startGesture(getCenter(from));
Offset offset = Offset.zero;
switch (position) {
case DraggableHoverPosition.center:
offset = getCenter(to);
break;
case DraggableHoverPosition.top:
offset = getTopLeft(to);
break;
case DraggableHoverPosition.bottom:
offset = getBottomLeft(to);
break;
default:
}
await gesture.moveTo(offset, timeStamp: const Duration(milliseconds: 400));
await gesture.up();
await pumpAndSettle();
}
}
extension ViewLayoutPBTest on ViewLayoutPB {
@@ -6,6 +6,7 @@ import 'package:appflowy/plugins/database_view/calendar/application/calendar_blo
import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_day.dart';
import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_page.dart';
import 'package:appflowy/plugins/database_view/calendar/presentation/toolbar/calendar_layout_setting.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/checkbox.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/checklist/checklist.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/select_option/option_list.dart';
@@ -14,31 +15,42 @@ import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/create_filter_list.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/disclosure_button.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/filter_menu_item.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/footer/grid_footer.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_cell.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_cell_action_sheet.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_list.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_option_editor.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/type_option/date.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/row/row.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/create_sort_list.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/order_panel.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/sort_editor.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/sort_menu.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/type_option/date.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/filter_button.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/grid_layout.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/sort_button.dart';
import 'package:appflowy/plugins/database_view/tar_bar/tab_bar_view.dart';
import 'package:appflowy/plugins/database_view/tar_bar/tar_bar_add_button.dart';
import 'package:appflowy/plugins/database_view/widgets/database_layout_ext.dart';
import 'package:appflowy/plugins/database_view/widgets/row/accessory/cell_accessory.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/cells.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/checklist_cell/checklist_progress_bar.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/date_cell/date_editor.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/select_option_editor.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/text_field.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/checklist_cell/checklist_progress_bar.dart';
import 'package:appflowy/plugins/database_view/widgets/row/row_action.dart';
import 'package:appflowy/plugins/database_view/widgets/row/row_banner.dart';
import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart';
import 'package:appflowy/plugins/database_view/widgets/row/row_document.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/date_cell/date_editor.dart';
import 'package:appflowy/plugins/database_view/widgets/setting/database_setting.dart';
import 'package:appflowy/plugins/database_view/widgets/setting/setting_button.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/emoji_picker/emoji_menu_item.dart';
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:calendar_view/calendar_view.dart';
@@ -50,25 +62,12 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/footer/grid_footer.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_cell.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/row/row.dart';
import 'package:appflowy/plugins/database_view/widgets/row/accessory/cell_accessory.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/cells.dart';
import 'package:appflowy/plugins/database_view/widgets/row/row_action.dart';
import 'package:appflowy/plugins/database_view/widgets/row/row_banner.dart';
import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/emoji_picker/emoji_menu_item.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
import 'package:path/path.dart' as p;
import 'package:table_calendar/table_calendar.dart';
import 'base.dart';
import 'common_operations.dart';
import 'expectation.dart';
import 'package:path/path.dart' as p;
import 'mock/mock_file_picker.dart';
extension AppFlowyDatabaseTest on WidgetTester {
@@ -77,9 +76,9 @@ extension AppFlowyDatabaseTest on WidgetTester {
await tapGoButton();
// expect to see a readme page
expectToSeePageName(readme);
expectToSeePageName(gettingStated);
await tapAddButton();
await tapAddViewButton();
await tapImportButton();
final testFileNames = ['v020.afdb'];
@@ -103,7 +102,8 @@ extension AppFlowyDatabaseTest on WidgetTester {
paths: paths,
);
await tapDatabaseRawDataButton();
await openPage('v020');
await pumpAndSettle();
await openPage('v020', layout: ViewLayoutPB.Grid);
}
Future<void> hoverOnFirstRowOfGrid() async {
@@ -358,6 +358,16 @@ extension AppFlowyDatabaseTest on WidgetTester {
await tapButton(findNewDateFormat);
}
Future<void> clearDate() async {
final findDateEditor = find.byType(DateCellEditor);
final findClearButton = find.byType(ClearDateButton);
final finder = find.descendant(
of: findDateEditor,
matching: findClearButton,
);
await tapButton(finder);
}
Future<void> tapSelectOptionCellInGrid({
required int rowIndex,
required FieldType fieldType,
@@ -2,30 +2,53 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/banner.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
import 'package:appflowy/workspace/presentation/home/home_stack.dart';
import 'package:appflowy/workspace/presentation/home/menu/app/section/item.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter_test/flutter_test.dart';
const String readme = 'Read me';
// const String readme = 'Read me';
const String gettingStated = '⭐️ Getting started';
extension Expectation on WidgetTester {
/// Expect to see the home page and with a default read me page.
void expectToSeeHomePage() {
expect(find.byType(HomeStack), findsOneWidget);
expect(find.textContaining(readme), findsWidgets);
expect(find.textContaining(gettingStated), findsWidgets);
}
/// Expect to see the page name on the home page.
void expectToSeePageName(String name) {
final pageName = findPageName(name);
void expectToSeePageName(
String name, {
String? parentName,
ViewLayoutPB layout = ViewLayoutPB.Document,
ViewLayoutPB parentLayout = ViewLayoutPB.Document,
}) {
final pageName = findPageName(
name,
layout: layout,
parentName: parentName,
parentLayout: parentLayout,
);
expect(pageName, findsOneWidget);
}
/// Expect not to see the page name on the home page.
void expectNotToSeePageName(String name) {
final pageName = findPageName(name);
void expectNotToSeePageName(
String name, {
String? parentName,
ViewLayoutPB layout = ViewLayoutPB.Document,
ViewLayoutPB parentLayout = ViewLayoutPB.Document,
}) {
final pageName = findPageName(
name,
layout: layout,
parentName: parentName,
parentLayout: parentLayout,
);
expect(pageName, findsNothing);
}
@@ -125,11 +148,49 @@ extension Expectation on WidgetTester {
expect(textWidget, findsOneWidget);
}
/// Find the page name on the home page.
Finder findPageName(String name) {
/// Find if the page is favorite
Finder findFavoritePageName(
String name, {
ViewLayoutPB layout = ViewLayoutPB.Document,
String? parentName,
ViewLayoutPB parentLayout = ViewLayoutPB.Document,
}) {
return find.byWidgetPredicate(
(widget) => widget is ViewSectionItem && widget.view.name == name,
(widget) =>
widget is ViewItem &&
widget.view.isFavorite &&
widget.categoryType == FolderCategoryType.favorite &&
widget.view.name == name &&
widget.view.layout == layout,
skipOffstage: false,
);
}
Finder findPageName(
String name, {
ViewLayoutPB layout = ViewLayoutPB.Document,
String? parentName,
ViewLayoutPB parentLayout = ViewLayoutPB.Document,
}) {
if (parentName == null) {
return find.byWidgetPredicate(
(widget) =>
widget is SingleInnerViewItem &&
widget.view.name == name &&
widget.view.layout == layout,
skipOffstage: false,
);
}
return find.descendant(
of: find.byWidgetPredicate(
(widget) =>
widget is ViewItem &&
widget.view.name == parentName &&
widget.view.layout == parentLayout,
skipOffstage: false,
),
matching: findPageName(name, layout: layout),
);
}
}
@@ -77,13 +77,6 @@ Future<String> mockSaveFilePath(
Future<List<String>> mockPickFilePaths({
required List<String> paths,
}) async {
// late final Directory dir;
// if (customPath != null) {
// dir = Directory(customPath);
// } else {
// dir = await TestFolder.testLocation(applicationDataPath, name);
// }
// final paths = fileNames.map((e) => p.join(dir.path, e)).toList();
getIt.unregister<FilePickerService>();
getIt.registerFactory<FilePickerService>(
() => MockFilePicker(
@@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>9.0</string>
<string>11.0</string>
</dict>
</plist>
+1 -1
View File
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'
# platform :ios, '11.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+134 -52
View File
@@ -1,81 +1,163 @@
PODS:
- flowy_editor (0.0.1):
- Flutter
- flowy_infra_ui (0.0.1):
- app_links (0.0.1):
- Flutter
- appflowy_backend (0.0.1):
- Flutter
- connectivity_plus (0.0.1):
- Flutter
- ReachabilitySwift
- device_info_plus (0.0.1):
- Flutter
- DKImagePickerController/Core (4.3.4):
- DKImagePickerController/ImageDataManager
- DKImagePickerController/Resource
- DKImagePickerController/ImageDataManager (4.3.4)
- DKImagePickerController/PhotoGallery (4.3.4):
- DKImagePickerController/Core
- DKPhotoGallery
- DKImagePickerController/Resource (4.3.4)
- DKPhotoGallery (0.0.17):
- DKPhotoGallery/Core (= 0.0.17)
- DKPhotoGallery/Model (= 0.0.17)
- DKPhotoGallery/Preview (= 0.0.17)
- DKPhotoGallery/Resource (= 0.0.17)
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Core (0.0.17):
- DKPhotoGallery/Model
- DKPhotoGallery/Preview
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Model (0.0.17):
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Preview (0.0.17):
- DKPhotoGallery/Model
- DKPhotoGallery/Resource
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Resource (0.0.17):
- SDWebImage
- SwiftyGif
- file_picker (0.0.1):
- DKImagePickerController/PhotoGallery
- Flutter
- flowy_infra_ui (0.0.1):
- Flutter
- Flutter (1.0.0)
- flutter_inappwebview (0.0.1):
- fluttertoast (0.0.2):
- Flutter
- flutter_inappwebview/Core (= 0.0.1)
- OrderedSet (~> 5.0)
- flutter_inappwebview/Core (0.0.1):
- Toast
- integration_test (0.0.1):
- Flutter
- OrderedSet (~> 5.0)
- flutter_keyboard_visibility (0.0.1):
- package_info_plus (0.4.5):
- Flutter
- image_picker (0.0.1):
- path_provider_foundation (0.0.1):
- Flutter
- OrderedSet (5.0.0)
- path_provider (0.0.1):
- FlutterMacOS
- ReachabilitySwift (5.0.0)
- rich_clipboard_ios (0.0.1):
- Flutter
- url_launcher (0.0.1):
- SDWebImage (5.14.2):
- SDWebImage/Core (= 5.14.2)
- SDWebImage/Core (5.14.2)
- shared_preferences_foundation (0.0.1):
- Flutter
- video_player (0.0.1):
- FlutterMacOS
- sign_in_with_apple (0.0.1):
- Flutter
- SwiftyGif (5.4.3)
- Toast (4.0.0)
- url_launcher_ios (0.0.1):
- Flutter
- webview_flutter_wkwebview (0.0.1):
- Flutter
DEPENDENCIES:
- flowy_editor (from `.symlinks/plugins/flowy_editor/ios`)
- flowy_infra_ui (from `.symlinks/plugins/flowy_infra_ui/ios`)
- app_links (from `.symlinks/plugins/app_links/ios`)
- appflowy_backend (from `.symlinks/plugins/appflowy_backend/ios`)
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- flowy_infra_ui (from `.symlinks/plugins/flowy_infra_ui/ios`)
- Flutter (from `Flutter`)
- flutter_inappwebview (from `.symlinks/plugins/flutter_inappwebview/ios`)
- flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`)
- image_picker (from `.symlinks/plugins/image_picker/ios`)
- path_provider (from `.symlinks/plugins/path_provider/ios`)
- url_launcher (from `.symlinks/plugins/url_launcher/ios`)
- video_player (from `.symlinks/plugins/video_player/ios`)
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
- integration_test (from `.symlinks/plugins/integration_test/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- rich_clipboard_ios (from `.symlinks/plugins/rich_clipboard_ios/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sign_in_with_apple (from `.symlinks/plugins/sign_in_with_apple/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`)
SPEC REPOS:
trunk:
- OrderedSet
- DKImagePickerController
- DKPhotoGallery
- ReachabilitySwift
- SDWebImage
- SwiftyGif
- Toast
EXTERNAL SOURCES:
flowy_editor:
:path: ".symlinks/plugins/flowy_editor/ios"
flowy_infra_ui:
:path: ".symlinks/plugins/flowy_infra_ui/ios"
app_links:
:path: ".symlinks/plugins/app_links/ios"
appflowy_backend:
:path: ".symlinks/plugins/appflowy_backend/ios"
connectivity_plus:
:path: ".symlinks/plugins/connectivity_plus/ios"
device_info_plus:
:path: ".symlinks/plugins/device_info_plus/ios"
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
flowy_infra_ui:
:path: ".symlinks/plugins/flowy_infra_ui/ios"
Flutter:
:path: Flutter
flutter_inappwebview:
:path: ".symlinks/plugins/flutter_inappwebview/ios"
flutter_keyboard_visibility:
:path: ".symlinks/plugins/flutter_keyboard_visibility/ios"
image_picker:
:path: ".symlinks/plugins/image_picker/ios"
path_provider:
:path: ".symlinks/plugins/path_provider/ios"
url_launcher:
:path: ".symlinks/plugins/url_launcher/ios"
video_player:
:path: ".symlinks/plugins/video_player/ios"
fluttertoast:
:path: ".symlinks/plugins/fluttertoast/ios"
integration_test:
:path: ".symlinks/plugins/integration_test/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
rich_clipboard_ios:
:path: ".symlinks/plugins/rich_clipboard_ios/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
sign_in_with_apple:
:path: ".symlinks/plugins/sign_in_with_apple/ios"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
webview_flutter_wkwebview:
:path: ".symlinks/plugins/webview_flutter_wkwebview/ios"
SPEC CHECKSUMS:
flowy_editor: bf8d58894ddb03453bd4d8521c57267ad638b837
flowy_infra_ui: 146c88346fd55d2ee6a41ae35059a5bf095cfbb3
appflowy_backend: c416222c639e678828776789bf0c1a1d0d59df3c
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
flutter_inappwebview: bfd58618f49dc62f2676de690fc6dcda1d6c3721
flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069
image_picker: 9aa50e1d8cdacdbed739e925b7eea16d014367e6
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
video_player: ecd305f42e9044793efd34846e1ce64c31ea6fcb
app_links: 5ef33d0d295a89d9d16bb81b0e3b0d5f70d6c875
appflowy_backend: 144c20d8bfb298c4e10fa3fa6701a9f41bf98b88
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
device_info_plus: 7545d84d8d1b896cb16a4ff98c19f07ec4b298ea
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
file_picker: ce3938a0df3cc1ef404671531facef740d03f920
flowy_infra_ui: 0455e1fa8c51885aa1437848e361e99419f34ebc
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
fluttertoast: fafc4fa4d01a6a9e4f772ecd190ffa525e9e2d9c
integration_test: 13825b8a9334a850581300559b8839134b124670
package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
rich_clipboard_ios: 7588abe18f881a6d0e9ec0b12e51cae2761e8942
SDWebImage: b9a731e1d6307f44ca703b3976d18c24ca561e84
shared_preferences_foundation: e2dae3258e06f44cc55f49d42024fd8dd03c590c
sign_in_with_apple: f3bf75217ea4c2c8b91823f225d70230119b8440
SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
webview_flutter_wkwebview: 2e2d318f21a5e036e2c3f26171342e95908bd60a
PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c
PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
COCOAPODS: 1.10.1
COCOAPODS: 1.11.3
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
@@ -217,10 +217,12 @@
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
@@ -231,6 +233,7 @@
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@@ -340,7 +343,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -414,7 +417,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -463,7 +466,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -47,5 +47,7 @@
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>
@@ -29,4 +29,16 @@ class KVKeys {
'kDocumentAppearanceFontSize';
static const String kDocumentAppearanceFontFamily =
'kDocumentAppearanceFontFamily';
/// The key for saving the expanded views
///
/// The value is a json string with the following format:
/// {'viewId': true, 'viewId2': false}
static const String expandedViews = 'expandedViews';
/// The key for saving the expanded folder
///
/// The value is a json string with the following format:
/// {'SidebarFolderCategoryType.value': true}
static const String expandedFolders = 'expandedFolders';
}
@@ -1,9 +1,9 @@
import 'dart:async';
import 'package:appflowy_backend/protobuf/flowy-user/user_setting.pb.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-net/network_state.pb.dart';
import 'package:flutter/services.dart';
class NetworkListener {
@@ -46,12 +46,12 @@ class NetworkListener {
return NetworkTypePB.VPN;
case ConnectivityResult.none:
case ConnectivityResult.other:
return NetworkTypePB.Unknown;
return NetworkTypePB.NetworkUnknown;
}
}();
Log.info("Network type: $networkType");
final state = NetworkStatePB.create()..ty = networkType;
NetworkEventUpdateNetworkType(state).send().then((result) {
UserEventUpdateNetworkState(state).send().then((result) {
result.fold(
(l) {},
(e) => Log.error(e),
+12 -50
View File
@@ -1,4 +1,5 @@
// lib/env/env.dart
import 'package:appflowy/startup/startup.dart';
import 'package:envied/envied.dart';
part 'env.g.dart';
@@ -28,61 +29,22 @@ abstract class Env {
defaultValue: '',
)
static final String supabaseAnonKey = _Env.supabaseAnonKey;
@EnviedField(
obfuscate: true,
varName: 'SUPABASE_KEY',
defaultValue: '',
)
static final String supabaseKey = _Env.supabaseKey;
@EnviedField(
obfuscate: true,
varName: 'SUPABASE_JWT_SECRET',
defaultValue: '',
)
static final String supabaseJwtSecret = _Env.supabaseJwtSecret;
@EnviedField(
obfuscate: true,
varName: 'SUPABASE_DB',
defaultValue: '',
)
static final String supabaseDb = _Env.supabaseDb;
@EnviedField(
obfuscate: true,
varName: 'SUPABASE_DB_USER',
defaultValue: '',
)
static final String supabaseDbUser = _Env.supabaseDbUser;
@EnviedField(
obfuscate: true,
varName: 'SUPABASE_DB_PASSWORD',
defaultValue: '',
)
static final String supabaseDbPassword = _Env.supabaseDbPassword;
@EnviedField(
obfuscate: true,
varName: 'SUPABASE_DB_PORT',
defaultValue: '5432',
)
static final String supabaseDbPort = _Env.supabaseDbPort;
@EnviedField(
obfuscate: true,
varName: 'ENABLE_SUPABASE_SYNC',
defaultValue: true,
)
static final bool enableSupabaseSync = _Env.enableSupabaseSync;
}
bool get isSupabaseEnable => false;
// Env.supabaseUrl.isNotEmpty &&
// Env.supabaseAnonKey.isNotEmpty &&
// Env.supabaseKey.isNotEmpty &&
// Env.supabaseJwtSecret.isNotEmpty &&
// Env.supabaseDb.isNotEmpty &&
// Env.supabaseDbUser.isNotEmpty &&
// Env.supabaseDbPassword.isNotEmpty &&
// Env.supabaseDbPort.isNotEmpty;
bool get isSupabaseEnabled {
// Only enable supabase in release and develop mode.
if (integrationEnv().isRelease || integrationEnv().isDevelop) {
return Env.supabaseUrl.isNotEmpty &&
Env.supabaseAnonKey.isNotEmpty &&
Env.supabaseJwtSecret.isNotEmpty;
} else {
return false;
}
}
@@ -46,7 +46,8 @@ class BlankPagePluginWidgetBuilder extends PluginWidgetBuilder
Widget tabBarItem(String pluginId) => leftBarItem;
@override
Widget buildWidget({PluginContext? context}) => const BlankPage();
Widget buildWidget({PluginContext? context, required bool shrinkWrap}) =>
const BlankPage();
@override
List<NavigationItem> get navigationItems => [this];
@@ -35,6 +35,7 @@ class DateCellData with _$DateCellData {
DateTime? dateTime,
String? time,
required bool includeTime,
bool? clearFlag,
}) = _DateCellData;
}
@@ -55,6 +56,9 @@ class DateCellDataPersistence implements CellDataPersistence<DateCellData> {
if (data.time != null) {
payload.time = data.time!;
}
if (data.clearFlag != null) {
payload.clearFlag = data.clearFlag!;
}
payload.includeTime = data.includeTime;
return DatabaseEventUpdateDateCell(payload).send().then((result) {
@@ -790,6 +790,7 @@ class FieldInfo {
case FieldType.RichText:
case FieldType.Checkbox:
case FieldType.Number:
case FieldType.DateTime:
return true;
default:
return false;
@@ -35,6 +35,7 @@ class BoardPageTabBarBuilderImpl implements DatabaseTabBarItemBuilder {
BuildContext context,
ViewPB view,
DatabaseController controller,
bool shrinkWrap,
) {
return BoardPage(
key: _makeValueKey(controller),
@@ -298,6 +299,7 @@ class _BoardContentState extends State<BoardContent> {
rowCache: rowCache,
cardData: groupData.group.groupId,
groupingFieldId: groupItem.fieldInfo.id,
groupId: groupData.group.groupId,
isEditing: isEditing,
cellBuilder: cellBuilder,
renderHook: renderHook,
@@ -27,11 +27,13 @@ class CalendarPageTabBarBuilderImpl implements DatabaseTabBarItemBuilder {
BuildContext context,
ViewPB view,
DatabaseController controller,
bool shrinkWrap,
) {
return CalendarPage(
key: _makeValueKey(controller),
view: view,
databaseController: controller,
shrinkWrap: shrinkWrap,
);
}
@@ -59,9 +61,11 @@ class CalendarPageTabBarBuilderImpl implements DatabaseTabBarItemBuilder {
class CalendarPage extends StatefulWidget {
final ViewPB view;
final DatabaseController databaseController;
final bool shrinkWrap;
const CalendarPage({
required this.view,
required this.databaseController,
this.shrinkWrap = false,
super.key,
});
@@ -95,12 +99,8 @@ class _CalendarPageState extends State<CalendarPage> {
Widget build(BuildContext context) {
return CalendarControllerProvider(
controller: _eventController,
child: MultiBlocProvider(
providers: [
BlocProvider<CalendarBloc>.value(
value: _calendarBloc,
)
],
child: BlocProvider<CalendarBloc>.value(
value: _calendarBloc,
child: MultiBlocListener(
listeners: [
BlocListener<CalendarBloc, CalendarState>(
@@ -159,14 +159,10 @@ class _CalendarPageState extends State<CalendarPage> {
],
child: BlocBuilder<CalendarBloc, CalendarState>(
builder: (context, state) {
return Column(
children: [
_buildCalendar(
_eventController,
state.settings
.foldLeft(0, (previous, a) => a.firstDayOfWeek),
),
],
return _buildCalendar(
context,
_eventController,
state.settings.foldLeft(0, (previous, a) => a.firstDayOfWeek),
);
},
),
@@ -175,19 +171,26 @@ class _CalendarPageState extends State<CalendarPage> {
);
}
Widget _buildCalendar(EventController eventController, int firstDayOfWeek) {
return Expanded(
child: Padding(
padding: GridSize.contentInsets,
child: MonthView(
Widget _buildCalendar(
BuildContext context,
EventController eventController,
int firstDayOfWeek,
) {
return Padding(
padding: GridSize.contentInsets,
child: LayoutBuilder(
// must specify MonthView width for useAvailableVerticalSpace to work properly
builder: (context, constraints) => MonthView(
key: _calendarState,
controller: _eventController,
cellAspectRatio: .6,
width: constraints.maxWidth,
cellAspectRatio: 0.6,
startDay: _weekdayFromInt(firstDayOfWeek),
borderColor: Theme.of(context).dividerColor,
headerBuilder: _headerNavigatorBuilder,
weekDayBuilder: _headerWeekDayBuilder,
cellBuilder: _calendarDayBuilder,
useAvailableVerticalSpace: widget.shrinkWrap,
),
),
);
@@ -3,7 +3,7 @@ import 'package:flutter/widgets.dart';
class CalendarSize {
static double scale = 1;
static double get scrollBarSize => 12 * scale;
static double get scrollBarSize => 8 * scale;
static double get navigatorButtonWidth => 20 * scale;
static double get navigatorButtonHeight => 25 * scale;
static EdgeInsets get daysOfWeekInsets =>
@@ -15,6 +15,7 @@ class RowActionSheetBloc
RowActionSheetBloc({
required String viewId,
required RowId rowId,
String? groupId,
}) : _rowService = RowBackendService(viewId: viewId),
super(RowActionSheetState.initial(rowId)) {
on<RowActionSheetEvent>(
@@ -25,7 +26,10 @@ class RowActionSheetBloc
logResult(result);
},
duplicateRow: () async {
final result = await _rowService.duplicateRow(rowId: state.rowId);
final result = await _rowService.duplicateRow(
rowId: state.rowId,
groupId: groupId,
);
logResult(result);
},
);
@@ -50,6 +50,7 @@ class GridPageTabBarBuilderImpl implements DatabaseTabBarItemBuilder {
BuildContext context,
ViewPB view,
DatabaseController controller,
bool shrinkWrap,
) {
return GridPage(
key: _makeValueKey(controller),
@@ -3,7 +3,7 @@ import 'package:flutter/widgets.dart';
class GridSize {
static double scale = 1;
static double get scrollBarSize => 12 * scale;
static double get scrollBarSize => 8 * scale;
static double get headerHeight => 40 * scale;
static double get footerHeight => 40 * scale;
static double get leadingHeaderPadding => 50 * scale;
@@ -107,7 +107,12 @@ class _CheckboxFilterEditorState extends State<CheckboxFilterEditor> {
height: 20,
child: Row(
children: [
FlowyText(state.filterInfo.fieldInfo.name),
Expanded(
child: FlowyText(
state.filterInfo.fieldInfo.name,
overflow: TextOverflow.ellipsis,
),
),
const HSpace(4),
CheckboxFilterConditionList(
filterInfo: state.filterInfo,
@@ -118,7 +123,6 @@ class _CheckboxFilterEditorState extends State<CheckboxFilterEditor> {
.add(CheckboxFilterEditorEvent.updateCondition(condition));
},
),
const Spacer(),
DisclosureButton(
popoverMutex: popoverMutex,
onAction: (action) {
@@ -91,12 +91,16 @@ class ChecklistState extends State<ChecklistFilterEditor> {
height: 20,
child: Row(
children: [
FlowyText(state.filterInfo.fieldInfo.name),
Expanded(
child: FlowyText(
state.filterInfo.fieldInfo.name,
overflow: TextOverflow.ellipsis,
),
),
const HSpace(4),
ChecklistFilterConditionList(
filterInfo: state.filterInfo,
),
const Spacer(),
DisclosureButton(
popoverMutex: widget.popoverMutex,
onAction: (action) {
@@ -142,7 +142,12 @@ class _SelectOptionFilterEditorState extends State<SelectOptionFilterEditor> {
height: 20,
child: Row(
children: [
FlowyText(state.filterInfo.fieldInfo.name),
Expanded(
child: FlowyText(
state.filterInfo.fieldInfo.name,
overflow: TextOverflow.ellipsis,
),
),
const HSpace(4),
SelectOptionFilterConditionList(
filterInfo: state.filterInfo,
@@ -153,7 +158,6 @@ class _SelectOptionFilterEditorState extends State<SelectOptionFilterEditor> {
);
},
),
const Spacer(),
DisclosureButton(
popoverMutex: popoverMutex,
onAction: (action) {
@@ -16,16 +16,22 @@ import '../../layout/sizes.dart';
class RowActions extends StatelessWidget {
final String viewId;
final RowId rowId;
final String? groupId;
const RowActions({
required this.viewId,
required this.rowId,
this.groupId,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => RowActionSheetBloc(viewId: viewId, rowId: rowId),
create: (context) => RowActionSheetBloc(
viewId: viewId,
rowId: rowId,
groupId: groupId,
),
child: BlocBuilder<RowActionSheetBloc, RowActionSheetState>(
builder: (context, state) {
final cells = _RowAction.values
@@ -110,7 +116,6 @@ extension _RowActionExtension on _RowAction {
bool enable() {
switch (this) {
case _RowAction.duplicate:
return false;
case _RowAction.delete:
return true;
}
@@ -31,6 +31,7 @@ abstract class DatabaseTabBarItemBuilder {
BuildContext context,
ViewPB view,
DatabaseController controller,
bool shrinkWrap,
);
/// Returns the setting bar of the tab bar item. The setting bar is shown on the
@@ -48,8 +49,10 @@ abstract class DatabaseTabBarItemBuilder {
class DatabaseTabBarView extends StatefulWidget {
final ViewPB view;
final bool shrinkWrap;
const DatabaseTabBarView({
required this.view,
required this.shrinkWrap,
super.key,
});
@@ -146,6 +149,7 @@ class _DatabaseTabBarViewState extends State<DatabaseTabBarView> {
context,
tabBar.view,
controller,
widget.shrinkWrap,
);
}).toList();
}
@@ -215,7 +219,7 @@ class DatabasePluginWidgetBuilder extends PluginWidgetBuilder {
Widget tabBarItem(String pluginId) => ViewTabBarItem(view: notifier.view);
@override
Widget buildWidget({PluginContext? context}) {
Widget buildWidget({PluginContext? context, required bool shrinkWrap}) {
notifier.isDeleted.addListener(() {
notifier.isDeleted.value.fold(() => null, (deletedView) {
if (deletedView.hasIndex()) {
@@ -226,6 +230,7 @@ class DatabasePluginWidgetBuilder extends PluginWidgetBuilder {
return DatabaseTabBarView(
key: ValueKey(notifier.view.id),
view: notifier.view,
shrinkWrap: shrinkWrap,
);
}
@@ -20,6 +20,7 @@ class RowCard<CustomCardData> extends StatefulWidget {
final RowMetaPB rowMeta;
final String viewId;
final String? groupingFieldId;
final String? groupId;
/// Allows passing a custom card data object to the card. The card will be
/// returned in the [CardCellBuilder] and can be used to build the card.
@@ -49,6 +50,7 @@ class RowCard<CustomCardData> extends StatefulWidget {
required this.rowMeta,
required this.viewId,
this.groupingFieldId,
this.groupId,
required this.isEditing,
required this.rowCache,
required this.cellBuilder,
@@ -137,7 +139,7 @@ class _RowCardState<T> extends State<RowCard<T>> {
} else {
return [
_CardEditOption(rowNotifier: rowNotifier),
_CardMoreOption(),
CardMoreOption(),
];
}
},
@@ -180,6 +182,7 @@ class _RowCardState<T> extends State<RowCard<T>> {
return RowActions(
viewId: context.read<CardBloc>().viewId,
rowId: context.read<CardBloc>().rowMeta.id,
groupId: widget.groupId,
);
}
}
@@ -269,8 +272,8 @@ class _CardContent<CustomCardData> extends StatelessWidget {
}
}
class _CardMoreOption extends StatelessWidget with CardAccessory {
_CardMoreOption({Key? key}) : super(key: key);
class CardMoreOption extends StatelessWidget with CardAccessory {
CardMoreOption({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
@@ -4,13 +4,15 @@ import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:flutter/material.dart';
class DatabaseViewWidget extends StatefulWidget {
final ViewPB view;
final bool shrinkWrap;
const DatabaseViewWidget({
super.key,
required this.view,
this.shrinkWrap = true,
});
final ViewPB view;
@override
State<DatabaseViewWidget> createState() => _DatabaseViewWidgetState();
}
@@ -46,7 +48,10 @@ class _DatabaseViewWidgetState extends State<DatabaseViewWidget> {
return ValueListenableBuilder<ViewLayoutPB>(
valueListenable: _layoutTypeChangeNotifier,
builder: (_, __, ___) {
return view.plugin().widgetBuilder.buildWidget();
return view
.plugin()
.widgetBuilder
.buildWidget(shrinkWrap: widget.shrinkWrap);
},
);
}
@@ -1,18 +1,19 @@
import 'dart:async';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pb.dart';
import 'package:easy_localization/easy_localization.dart'
show StringTranslateExtension;
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-error/code.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:easy_localization/easy_localization.dart'
show StringTranslateExtension;
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:table_calendar/table_calendar.dart';
import 'dart:async';
import 'package:protobuf/protobuf.dart';
import 'package:table_calendar/table_calendar.dart';
part 'date_cal_bloc.freezed.dart';
@@ -64,6 +65,9 @@ class DateCellCalendarBloc
setFocusedDay: (focusedDay) {
emit(state.copyWith(focusedDay: focusedDay));
},
clearDate: () async {
await _clearDate(emit);
},
);
},
);
@@ -121,6 +125,29 @@ class DateCellCalendarBloc
);
}
Future<void> _clearDate(Emitter<DateCellCalendarState> emit) async {
final DateCellData newDateData = DateCellData(
dateTime: null,
time: null,
includeTime: state.includeTime,
clearFlag: true,
);
cellController.saveCellData(
newDateData,
onFinish: (result) {
result.fold(
() {
if (!isClosed) {
add(const DateCellCalendarEvent.didReceiveTimeFormatError(null));
}
},
(err) => Log.error(err),
);
},
);
}
DateTime? _utcToLocalAddTime(DateTime? date) {
if (date == null) {
return null;
@@ -237,6 +264,8 @@ class DateCellCalendarEvent with _$DateCellCalendarEvent {
_TimeFormat;
const factory DateCellCalendarEvent.setDateFormat(DateFormatPB dateFormat) =
_DateFormat;
const factory DateCellCalendarEvent.clearDate() = _ClearDate;
}
@freezed
@@ -1,9 +1,9 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import '../../../../grid/presentation/layout/sizes.dart';
import '../../cell_builder.dart';
@@ -83,7 +83,7 @@ class _DateCellState extends GridCellState<GridDateCell> {
controller: _popover,
triggerActions: PopoverTriggerFlags.none,
direction: PopoverDirection.bottomWithLeftAligned,
constraints: BoxConstraints.loose(const Size(260, 500)),
constraints: BoxConstraints.loose(const Size(260, 520)),
margin: EdgeInsets.zero,
child: dateTextWidget,
popupBuilder: (BuildContext popoverContent) {
@@ -1,9 +1,11 @@
import 'dart:async';
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
part 'date_cell_bloc.freezed.dart';
class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
@@ -85,5 +87,5 @@ String _dateStrFromCellData(DateCellDataPB? cellData) {
dateStr = cellData.date;
}
}
return dateStr;
return dateStr.trim();
}
@@ -3,20 +3,21 @@ import 'package:appflowy/plugins/database_view/application/cell/cell_controller_
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle_style.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pbserver.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:dartz/dartz.dart' show Either;
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra/time/duration.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pbserver.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:table_calendar/table_calendar.dart';
import '../../../../grid/presentation/layout/sizes.dart';
import '../../../../grid/presentation/widgets/common/type_option_separator.dart';
import '../../../../grid/presentation/widgets/header/type_option/date.dart';
@@ -123,7 +124,9 @@ class _CellCalendarWidgetState extends State<_CellCalendarWidget> {
const TypeOptionSeparator(spacing: 12.0),
const _IncludeTimeButton(),
const TypeOptionSeparator(spacing: 12.0),
DateTypeOptionButton(popoverMutex: popoverMutex)
DateTypeOptionButton(popoverMutex: popoverMutex),
const TypeOptionSeparator(spacing: 12.0),
const ClearDateButton(),
];
return ListView.builder(
@@ -458,3 +461,29 @@ class _CalDateTimeSettingState extends State<_CalDateTimeSetting> {
);
}
}
@visibleForTesting
class ClearDateButton extends StatelessWidget {
const ClearDateButton({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: SizedBox(
height: GridSize.popoverItemHeight,
child: FlowyButton(
text: FlowyText.medium(LocaleKeys.grid_field_clearDate.tr()),
onTap: () {
context
.read<DateCellCalendarBloc>()
.add(const DateCellCalendarEvent.clearDate());
PopoverContainer.of(context).close();
},
leftIcon: const FlowySvg(name: "grid/delete"),
margin: GridSize.typeOptionContentInsets,
),
),
);
}
}
@@ -82,7 +82,7 @@ class DocumentPluginWidgetBuilder extends PluginWidgetBuilder
EdgeInsets get contentPadding => EdgeInsets.zero;
@override
Widget buildWidget({PluginContext? context}) {
Widget buildWidget({PluginContext? context, required bool shrinkWrap}) {
notifier.isDeleted.addListener(() {
notifier.isDeleted.value.fold(() => null, (deletedView) {
if (deletedView.hasIndex()) {
@@ -1,6 +1,7 @@
import 'package:appflowy/plugins/document/application/doc_bloc.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
import 'package:appflowy/workspace/application/settings/shortcuts/settings_shortcuts_service.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/inline_page/inline_page_reference.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:collection/collection.dart';
@@ -31,6 +32,15 @@ class AppFlowyEditorPage extends StatefulWidget {
State<AppFlowyEditorPage> createState() => _AppFlowyEditorPageState();
}
final List<CommandShortcutEvent> commandShortcutEvents = [
...codeBlockCommands,
...standardCommandShortcutEvents,
];
final List<CommandShortcutEvent> defaultCommandShortcutEvents = [
...commandShortcutEvents.map((e) => e.copyWith()).toList(),
];
class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
late final ScrollController effectiveScrollController;
@@ -109,10 +119,10 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
void initState() {
super.initState();
_initializeShortcuts();
indentableBlockTypes.add(ToggleListBlockKeys.type);
convertibleBlockTypes.add(ToggleListBlockKeys.type);
slashMenuItems = _customSlashMenuItems();
effectiveScrollController = widget.scrollController ?? ScrollController();
}
@@ -224,13 +234,19 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
),
),
DatabaseBlockKeys.gridType: DatabaseViewBlockComponentBuilder(
configuration: configuration,
configuration: configuration.copyWith(
padding: (_) => const EdgeInsets.symmetric(vertical: 10),
),
),
DatabaseBlockKeys.boardType: DatabaseViewBlockComponentBuilder(
configuration: configuration,
configuration: configuration.copyWith(
padding: (_) => const EdgeInsets.symmetric(vertical: 10),
),
),
DatabaseBlockKeys.calendarType: DatabaseViewBlockComponentBuilder(
configuration: configuration,
configuration: configuration.copyWith(
padding: (_) => const EdgeInsets.symmetric(vertical: 10),
),
),
CalloutBlockKeys.type: CalloutBlockComponentBuilder(
configuration: configuration,
@@ -377,4 +393,16 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
}
return const (false, null);
}
Future<void> _initializeShortcuts() async {
//TODO(Xazin): Refactor lazy initialization
defaultCommandShortcutEvents;
final settingsShortcutService = SettingsShortcutService();
final customizeShortcuts =
await settingsShortcutService.getCustomizeShortcuts();
await settingsShortcutService.updateCommandShortcuts(
standardCommandShortcutEvents,
customizeShortcuts,
);
}
}
@@ -1,3 +1,5 @@
import 'dart:io';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra_ui/widget/ignore_parent_gesture.dart';
import 'package:flutter/material.dart';
@@ -22,7 +24,9 @@ class BlockActionButton extends StatelessWidget {
preferBelow: false,
richMessage: richMessage,
child: MouseRegion(
cursor: SystemMouseCursors.grab,
cursor: Platform.isWindows
? SystemMouseCursors.click
: SystemMouseCursors.grab,
child: IgnoreParentGestureWidget(
child: GestureDetector(
onTap: onTap,
@@ -1,4 +1,5 @@
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/startup/plugin/plugin.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
import 'package:appflowy/workspace/application/view/view_service.dart';
@@ -14,7 +15,6 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/presentation/home/menu/menu.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
@@ -90,7 +90,7 @@ class _BuiltInPageWidgetState extends State<BuiltInPageWidget> {
onEnter: (_) => widget.editorState.service.scrollService?.disable(),
onExit: (_) => widget.editorState.service.scrollService?.enable(),
child: SizedBox(
height: 400,
height: viewPB.pluginType == PluginType.calendar ? 700 : 400,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
@@ -154,11 +154,10 @@ class _BuiltInPageWidgetState extends State<BuiltInPageWidget> {
onSelected: (action, controller) async {
switch (action.inner) {
case _ActionType.viewDatabase:
getIt<MenuSharedState>().latestOpenView = viewPB;
getIt<TabsBloc>().add(
TabsEvent.openPlugin(
plugin: viewPB.plugin(),
view: viewPB,
),
);
break;
@@ -82,23 +82,16 @@ class _LinkToPageMenuState extends State<LinkToPageMenu> {
final _focusNode = FocusNode(debugLabel: 'reference_list_widget');
EditorStyle get style => widget.editorState.editorStyle;
int _selectedIndex = 0;
int _totalItems = 0;
Future<List<(ViewPB, List<ViewPB>)>>? _availableLayout;
final Map<int, (ViewPB, ViewPB)> _items = {};
final int _totalItems = 0;
Future<List<ViewPB>>? _availableLayout;
final List<ViewPB> _items = [];
Future<List<(ViewPB, List<ViewPB>)>> fetchItems() async {
Future<List<ViewPB>> fetchItems() async {
final items =
await ViewBackendService().fetchViewsWithLayoutType(widget.layoutType);
int index = 0;
for (final (app, children) in items) {
for (final view in children) {
_items.putIfAbsent(index, () => (app, view));
index += 1;
}
}
_totalItems = _items.length;
_items
..clear()
..addAll(items);
return items;
}
@@ -176,8 +169,8 @@ class _LinkToPageMenuState extends State<LinkToPageMenu> {
newSelectedIndex %= _totalItems;
} else if (event.logicalKey == LogicalKeyboardKey.enter) {
widget.onSelected(
_items[_selectedIndex]!.$1,
_items[_selectedIndex]!.$2,
_items[_selectedIndex],
_items[_selectedIndex],
);
}
@@ -191,10 +184,10 @@ class _LinkToPageMenuState extends State<LinkToPageMenu> {
Widget _buildListWidget(
BuildContext context,
int selectedIndex,
Future<List<(ViewPB, List<ViewPB>)>>? items,
Future<List<ViewPB>>? items,
) {
int index = 0;
return FutureBuilder<List<(ViewPB, List<ViewPB>)>>(
return FutureBuilder<List<ViewPB>>(
builder: (context, snapshot) {
if (snapshot.hasData &&
snapshot.connectionState == ConnectionState.done) {
@@ -211,35 +204,23 @@ class _LinkToPageMenuState extends State<LinkToPageMenu> {
];
if (views != null && views.isNotEmpty) {
for (final (view, viewChildren) in views) {
if (viewChildren.isNotEmpty) {
children.add(
Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: FlowyText.regular(
view.name,
),
for (final view in views) {
children.add(
FlowyButton(
isSelected: index == _selectedIndex,
leftIcon: svgWidget(
view.iconName,
color: Theme.of(context).iconTheme.color,
),
);
text: FlowyText.regular(view.name),
onTap: () => widget.onSelected(view, view),
),
);
for (final value in viewChildren) {
children.add(
FlowyButton(
isSelected: index == _selectedIndex,
leftIcon: svgWidget(
value.iconName,
color: Theme.of(context).iconTheme.color,
),
text: FlowyText.regular(value.name),
onTap: () => widget.onSelected(view, value),
),
);
index += 1;
}
}
index += 1;
}
}
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: children,
@@ -132,7 +132,13 @@ class _CalloutBlockComponentWidgetState
if (colorString == null) {
return Colors.transparent;
}
return colorString.toColor();
final brightness = Theme.of(context).brightness;
final bool isDarkMode = brightness == Brightness.dark;
final Color bgColor = colorString.toColor();
return isDarkMode ? bgColor.withOpacity(0.3) : bgColor;
}
// get the emoji of the note block from the node's attributes or default to '📌'
@@ -123,7 +123,7 @@ class _CodeBlockComponentWidgetState extends State<CodeBlockComponentWidget>
'BASIC',
'C',
'C#',
'C++',
'CPP',
'Clojure',
'CSS',
'Dart',
@@ -167,7 +167,10 @@ class _CodeBlockComponentWidgetState extends State<CodeBlockComponentWidget>
.map((e) => e.toLowerCase())
.toSet()
.intersection(allLanguages.keys.toSet())
.toList();
.toList()
..add('auto')
..add('c')
..sort();
late final editorState = context.read<EditorState>();
@@ -253,7 +256,7 @@ class _CodeBlockComponentWidgetState extends State<CodeBlockComponentWidget>
alignment: Alignment.centerLeft,
padding: const EdgeInsets.symmetric(horizontal: 4),
child: FlowyTextButton(
'${language?.capitalize() ?? 'auto'} ',
'${language?.capitalize() ?? 'Auto'} ',
padding: const EdgeInsets.symmetric(
horizontal: 12.0,
vertical: 4.0,
@@ -281,7 +284,7 @@ class _CodeBlockComponentWidgetState extends State<CodeBlockComponentWidget>
Future<void> updateLanguage(String language) async {
final transaction = editorState.transaction
..updateNode(node, {
CodeBlockKeys.language: language,
CodeBlockKeys.language: language == 'auto' ? null : language,
})
..afterSelection = Selection.collapse(
node.path,
@@ -78,10 +78,16 @@ class _DatabaseBlockComponentWidgetState
return DatabaseViewWidget(
key: ValueKey(viewPB.id),
view: viewPB,
shrinkWrap: true,
);
},
);
child = Padding(
padding: padding,
child: child,
);
if (widget.actionBuilder != null) {
child = BlockComponentActionWrapper(
node: widget.node,
@@ -17,11 +17,8 @@ SelectionMenuItem inlineGridMenuItem(DocumentBloc documentBloc) =>
),
keywords: ['grid', 'database'],
handler: (editorState, menuService, context) async {
if (!documentBloc.view.hasParentViewId()) {
return;
}
final parentViewId = documentBloc.view.parentViewId;
// create the view inside current page
final parentViewId = documentBloc.view.id;
ViewBackendService.createView(
parentViewId: parentViewId,
openAfterCreate: false,
@@ -45,11 +42,8 @@ SelectionMenuItem inlineBoardMenuItem(DocumentBloc documentBloc) =>
),
keywords: ['board', 'kanban', 'database'],
handler: (editorState, menuService, context) async {
if (!documentBloc.view.hasParentViewId()) {
return;
}
final parentViewId = documentBloc.view.parentViewId;
// create the view inside current page
final parentViewId = documentBloc.view.id;
ViewBackendService.createView(
parentViewId: parentViewId,
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
@@ -72,11 +66,8 @@ SelectionMenuItem inlineCalendarMenuItem(DocumentBloc documentBloc) =>
),
keywords: ['calendar', 'database'],
handler: (editorState, menuService, context) async {
if (!documentBloc.view.hasParentViewId()) {
return;
}
final parentViewId = documentBloc.view.parentViewId;
// create the view inside current page
final parentViewId = documentBloc.view.id;
ViewBackendService.createView(
parentViewId: parentViewId,
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
@@ -2,7 +2,6 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/base/selec
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/application/view/view_service.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
enum MentionType {
@@ -98,17 +97,11 @@ class InlinePageReferenceService {
Future<List<SelectionMenuItem>> generatePageItems(String character) async {
final service = ViewBackendService();
final List<(ViewPB, List<ViewPB>)> pbViews = await service.fetchViews(
(_, __) => true,
);
if (pbViews.isEmpty) {
final views = await service.fetchViews();
if (views.isEmpty) {
return [];
}
final List<SelectionMenuItem> pages = [];
final List<ViewPB> views = [];
for (final element in pbViews) {
views.addAll(element.$2);
}
views.sort(((a, b) => b.createTime.compareTo(a.createTime)));
for (final view in views) {
@@ -1,9 +1,9 @@
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
import 'package:appflowy/plugins/trash/application/trash_service.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
import 'package:appflowy/workspace/application/view/prelude.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/presentation/home/menu/menu.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
import 'package:appflowy_editor/appflowy_editor.dart'
@@ -108,7 +108,12 @@ class _MentionPageBlockState extends State<MentionPageBlock> {
Log.error('Page($pageId) not found');
return;
}
getIt<MenuSharedState>().latestOpenView = view;
getIt<TabsBloc>().add(
TabsEvent.openPlugin(
plugin: view.plugin(),
view: view,
),
);
}
Future<ViewPB?> fetchView(String pageId) async {
@@ -58,7 +58,8 @@ class TrashPluginDisplay extends PluginWidgetBuilder {
Widget? get rightBarItem => null;
@override
Widget buildWidget({PluginContext? context}) => const TrashPage(
Widget buildWidget({PluginContext? context, required bool shrinkWrap}) =>
const TrashPage(
key: ValueKey('TrashPage'),
);
@@ -1,5 +1,6 @@
import 'package:appflowy/core/config/kv.dart';
import 'package:appflowy/core/network_monitor.dart';
import 'package:appflowy/env/env.dart';
import 'package:appflowy/plugins/database_view/application/field/field_action_sheet_bloc.dart';
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
@@ -15,6 +16,7 @@ import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
import 'package:flowy_infra/file_picker/file_picker_impl.dart';
import 'package:flowy_infra/file_picker/file_picker_service.dart';
import 'package:appflowy/plugins/document/application/prelude.dart';
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
import 'package:appflowy/workspace/application/user/prelude.dart';
import 'package:appflowy/workspace/application/workspace/prelude.dart';
import 'package:appflowy/workspace/application/edit_panel/edit_panel_bloc.dart';
@@ -81,8 +83,11 @@ void _resolveCommonService(
}
void _resolveUserDeps(GetIt getIt) {
// getIt.registerFactory<AuthService>(() => AppFlowyAuthService());
getIt.registerFactory<AuthService>(() => SupabaseAuthService());
if (isSupabaseEnabled) {
getIt.registerFactory<AuthService>(() => SupabaseAuthService());
} else {
getIt.registerFactory<AuthService>(() => AppFlowyAuthService());
}
getIt.registerFactory<AuthRouter>(() => AuthRouter());
@@ -156,6 +161,7 @@ void _resolveFolderDeps(GetIt getIt) {
getIt.registerFactory<TrashBloc>(
() => TrashBloc(),
);
getIt.registerFactory<FavoriteBloc>(() => FavoriteBloc());
}
void _resolveDocDeps(GetIt getIt) {
@@ -66,7 +66,7 @@ abstract class PluginWidgetBuilder with NavigationItem {
EdgeInsets get contentPadding =>
const EdgeInsets.symmetric(horizontal: 40, vertical: 28);
Widget buildWidget({PluginContext? context});
Widget buildWidget({PluginContext? context, required bool shrinkWrap});
}
class PluginContext {
@@ -7,7 +7,7 @@ import 'package:appflowy/core/config/kv_keys.dart';
import 'package:appflowy/startup/startup.dart';
class WindowSizeManager {
static const double minWindowHeight = 400.0;
static const double minWindowHeight = 600.0;
static const double minWindowWidth = 800.0;
static const width = 'width';
@@ -1,3 +1,4 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:hotkey_manager/hotkey_manager.dart';
import '../startup.dart';
@@ -7,6 +8,10 @@ class HotKeyTask extends LaunchTask {
@override
Future<void> initialize(LaunchContext context) async {
// the hotkey manager is not supported on mobile
if (PlatformExtension.isMobile) {
return;
}
await hotKeyManager.unregisterAll();
}
}
@@ -30,19 +30,11 @@ class InitRustSDKTask extends LaunchTask {
}
AppFlowyEnv getAppFlowyEnv() {
final postgresConfig = PostgresConfiguration(
url: Env.supabaseDb,
password: Env.supabaseDbPassword,
port: int.parse(Env.supabaseDbPort),
user_name: Env.supabaseDbUser,
);
final supabaseConfig = SupabaseConfiguration(
enable_sync: Env.enableSupabaseSync,
enable_sync: true,
url: Env.supabaseUrl,
key: Env.supabaseKey,
anon_key: Env.supabaseAnonKey,
jwt_secret: Env.supabaseJwtSecret,
postgres_config: postgresConfig,
);
return AppFlowyEnv(
@@ -8,7 +8,7 @@ bool isSupabaseInitialized = false;
class InitSupabaseTask extends LaunchTask {
@override
Future<void> initialize(LaunchContext context) async {
if (!isSupabaseEnable) {
if (!isSupabaseEnabled) {
return;
}
@@ -18,7 +18,8 @@ class InitSupabaseTask extends LaunchTask {
await Supabase.initialize(
url: Env.supabaseUrl,
anonKey: Env.supabaseAnonKey,
debug: false,
debug: true,
// authFlowType: AuthFlowType.pkce,
);
isSupabaseInitialized = true;
@@ -75,12 +75,28 @@ class AppFlowyAuthService implements AuthService {
required String platform,
AuthTypePB authType = AuthTypePB.Local,
Map<String, String> map = const {},
}) {
throw UnimplementedError();
}) async {
return left(
FlowyError.create()
..code = 0
..msg = "Unsupported sign up action",
);
}
@override
Future<Either<FlowyError, UserProfilePB>> getUser() async {
return UserBackendService.getCurrentUserProfile();
}
@override
Future<Either<FlowyError, UserProfilePB>> signInWithMagicLink({
required String email,
Map<String, String> map = const {},
}) async {
return left(
FlowyError.create()
..code = 0
..msg = "Unsupported sign up action",
);
}
}
@@ -42,6 +42,11 @@ abstract class AuthService {
Map<String, String> map,
});
Future<Either<FlowyError, UserProfilePB>> signInWithMagicLink({
required String email,
Map<String, String> map,
});
///
Future<void> signOut();
@@ -9,9 +9,13 @@ import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:dartz/dartz.dart';
import 'package:flutter/foundation.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'auth_error.dart';
// can't use underscore here.
const loginCallback = 'io.appflowy.appflowy-flutter://login-callback';
class SupabaseAuthService implements AuthService {
SupabaseAuthService();
@@ -28,7 +32,7 @@ class SupabaseAuthService implements AuthService {
AuthTypePB authType = AuthTypePB.Supabase,
Map<String, String> map = const {},
}) async {
if (!isSupabaseEnable) {
if (!isSupabaseEnabled) {
return _appFlowyAuthService.signUp(
name: name,
email: email,
@@ -65,7 +69,7 @@ class SupabaseAuthService implements AuthService {
AuthTypePB authType = AuthTypePB.Supabase,
Map<String, String> map = const {},
}) async {
if (!isSupabaseEnable) {
if (!isSupabaseEnabled) {
return _appFlowyAuthService.signIn(
email: email,
password: password,
@@ -101,39 +105,25 @@ class SupabaseAuthService implements AuthService {
AuthTypePB authType = AuthTypePB.Supabase,
Map<String, String> map = const {},
}) async {
if (!isSupabaseEnable) {
return _appFlowyAuthService.signUpWithOAuth(
platform: platform,
);
if (!isSupabaseEnabled) {
return _appFlowyAuthService.signUpWithOAuth(platform: platform);
}
final provider = platform.toProvider();
final completer = Completer<Either<FlowyError, UserProfilePB>>();
late final StreamSubscription<AuthState> subscription;
subscription = _auth.onAuthStateChange.listen((event) async {
final user = event.session?.user;
if (event.event != AuthChangeEvent.signedIn || user == null) {
completer.complete(left(AuthError.supabaseSignInWithOauthError));
} else {
final Either<FlowyError, UserProfilePB> response = await setupAuth(
final completer = supabaseLoginCompleter(
onSuccess: (userId, userEmail) async {
return await setupAuth(
map: {
AuthServiceMapKeys.uuid: user.id,
AuthServiceMapKeys.email: user.email ?? user.newEmail ?? ''
AuthServiceMapKeys.uuid: userId,
AuthServiceMapKeys.email: userEmail
},
);
completer.complete(response);
}
subscription.cancel();
});
final Map<String, String> query = {};
if (provider == Provider.google) {
query['access_type'] = 'offline';
query['prompt'] = 'consent';
}
},
);
final response = await _auth.signInWithOAuth(
provider,
queryParams: query,
redirectTo:
'io.appflowy.appflowy-flutter://login-callback', // can't use underscore here.
queryParams: queryParamsForProvider(provider),
redirectTo: loginCallback,
);
if (!response) {
completer.complete(left(AuthError.supabaseSignInWithOauthError));
@@ -145,10 +135,9 @@ class SupabaseAuthService implements AuthService {
Future<void> signOut({
AuthTypePB authType = AuthTypePB.Supabase,
}) async {
if (!isSupabaseEnable) {
return _appFlowyAuthService.signOut();
if (isSupabaseEnabled) {
await _auth.signOut();
}
await _auth.signOut();
await _appFlowyAuthService.signOut(
authType: authType,
);
@@ -164,17 +153,28 @@ class SupabaseAuthService implements AuthService {
return _appFlowyAuthService.signUpAsGuest();
}
// @override
// Future<Either<FlowyError, UserProfilePB>> getUser() async {
// final loginType = await getIt<KeyValueStorage>()
// .get(KVKeys.loginType)
// .then((value) => value.toOption().toNullable());
// if (!isSupabaseEnable || (loginType != null && loginType != 'supabase')) {
// return _appFlowyAuthService.getUser();
// }
// final user = await getSupabaseUser();
// return user.map((r) => r.toUserProfile());
// }
@override
Future<Either<FlowyError, UserProfilePB>> signInWithMagicLink({
required String email,
Map<String, String> map = const {},
}) async {
final completer = supabaseLoginCompleter(
onSuccess: (userId, userEmail) async {
return await setupAuth(
map: {
AuthServiceMapKeys.uuid: userId,
AuthServiceMapKeys.email: userEmail
},
);
},
);
await _auth.signInWithOtp(
email: email,
emailRedirectTo: kIsWeb ? null : loginCallback,
);
return completer.future;
}
@override
Future<Either<FlowyError, UserProfilePB>> getUser() async {
@@ -216,3 +216,45 @@ extension on String {
}
}
}
Completer<Either<FlowyError, UserProfilePB>> supabaseLoginCompleter({
required Future<Either<FlowyError, UserProfilePB>> Function(
String userId,
String userEmail,
) onSuccess,
}) {
final completer = Completer<Either<FlowyError, UserProfilePB>>();
late final StreamSubscription<AuthState> subscription;
final auth = Supabase.instance.client.auth;
subscription = auth.onAuthStateChange.listen((event) async {
final user = event.session?.user;
if (event.event != AuthChangeEvent.signedIn || user == null) {
completer.complete(left(AuthError.supabaseSignInWithOauthError));
} else {
final response = await onSuccess(
user.id,
user.email ?? user.newEmail ?? '',
);
completer.complete(response);
}
subscription.cancel();
});
return completer;
}
Map<String, String> queryParamsForProvider(Provider provider) {
switch (provider) {
case Provider.github:
return {};
case Provider.google:
return {
'access_type': 'offline',
'prompt': 'consent',
};
case Provider.discord:
return {};
default:
return {};
}
}
@@ -48,6 +48,9 @@ class SignInBloc extends Bloc<SignInEvent, SignInState> {
),
);
},
signedWithMagicLink: (SignedWithMagicLink value) async {
await _performActionOnSignInWithMagicLink(state, emit, value.email);
},
);
});
}
@@ -99,6 +102,34 @@ class SignInBloc extends Bloc<SignInEvent, SignInState> {
);
}
Future<void> _performActionOnSignInWithMagicLink(
SignInState state,
Emitter<SignInState> emit,
String email,
) async {
emit(
state.copyWith(
isSubmitting: true,
emailError: none(),
passwordError: none(),
successOrFail: none(),
),
);
final result = await authService.signInWithMagicLink(
email: email,
);
emit(
result.fold(
(error) => stateFromCode(error),
(userProfile) => state.copyWith(
isSubmitting: false,
successOrFail: some(left(userProfile)),
),
),
);
}
Future<void> _performActionOnSignInAsGuest(
SignInState state,
Emitter<SignInState> emit,
@@ -125,7 +156,7 @@ class SignInBloc extends Bloc<SignInEvent, SignInState> {
}
SignInState stateFromCode(FlowyError error) {
switch (ErrorCode.valueOf(error.code)!) {
switch (ErrorCode.valueOf(error.code)) {
case ErrorCode.EmailFormatInvalid:
return state.copyWith(
isSubmitting: false,
@@ -154,6 +185,8 @@ class SignInEvent with _$SignInEvent {
const factory SignInEvent.signedInWithOAuth(String platform) =
SignedInWithOAuth;
const factory SignInEvent.signedInAsGuest() = SignedInAsGuest;
const factory SignInEvent.signedWithMagicLink(String email) =
SignedWithMagicLink;
const factory SignInEvent.emailChanged(String email) = EmailChanged;
const factory SignInEvent.passwordChanged(String password) = PasswordChanged;
}
@@ -1,5 +1,6 @@
import 'dart:async';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:dartz/dartz.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
@@ -16,7 +17,10 @@ class UserBackendService {
static Future<Either<FlowyError, UserProfilePB>>
getCurrentUserProfile() async {
final result = await UserEventGetUserProfile().send();
final result = await UserEventGetUserProfile().send().then((value) {
value.fold((l) => null, (r) => Log.error(r));
return value;
});
return result.swap();
}
@@ -171,23 +171,19 @@ class CreateFolderWidgetState extends State<CreateFolderWidget> {
const VSpace(4.0),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: Row(
children: [
_buildTextButton(
context,
LocaleKeys.settings_files_create.tr(),
() async {
if (_path.isEmpty) {
_showToast(
LocaleKeys.settings_files_locationCannotBeEmpty.tr(),
);
} else {
await getIt<ApplicationDataStorage>().setCustomPath(_path);
await widget.onPressedCreate();
}
},
),
],
child: _buildTextButton(
context,
LocaleKeys.settings_files_create.tr(),
() async {
if (_path.isEmpty) {
_showToast(
LocaleKeys.settings_files_locationCannotBeEmpty.tr(),
);
} else {
await getIt<ApplicationDataStorage>().setCustomPath(_path);
await widget.onPressedCreate();
}
},
),
)
],
@@ -218,13 +214,10 @@ Widget _buildTextButton(
String title,
VoidCallback onPressed,
) {
return SizedBox(
width: 60,
child: SecondaryTextButton(
title,
mode: SecondaryTextButtonMode.small,
onPressed: onPressed,
),
return SecondaryTextButton(
title,
mode: SecondaryTextButtonMode.small,
onPressed: onPressed,
);
}
@@ -243,17 +236,18 @@ class _FolderCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
const cardSpacing = 16.0;
return Card(
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 16.0,
horizontal: 16.0,
vertical: cardSpacing,
horizontal: cardSpacing,
),
child: Row(
children: [
if (icon != null)
Padding(
padding: const EdgeInsets.only(right: 20),
padding: const EdgeInsets.only(right: cardSpacing),
child: icon!,
),
Expanded(
@@ -280,7 +274,7 @@ class _FolderCard extends StatelessWidget {
),
),
if (trailing != null) ...[
const HSpace(40),
const HSpace(cardSpacing),
trailing!,
],
],
@@ -333,31 +333,31 @@ class ThirdPartySignInButtons extends StatelessWidget {
icon: 'login/google-mark',
onPressed: () {
getIt<KeyValueStorage>().set(KVKeys.loginType, 'supabase');
context
.read<SignInBloc>()
.add(const SignInEvent.signedInWithOAuth('google'));
},
),
const SizedBox(width: 20),
ThirdPartySignInButton(
icon: 'login/github-mark',
onPressed: () {
getIt<KeyValueStorage>().set(KVKeys.loginType, 'supabase');
context
.read<SignInBloc>()
.add(const SignInEvent.signedInWithOAuth('github'));
},
),
const SizedBox(width: 20),
ThirdPartySignInButton(
icon: 'login/discord-mark',
onPressed: () {
getIt<KeyValueStorage>().set(KVKeys.loginType, 'supabase');
context
.read<SignInBloc>()
.add(const SignInEvent.signedInWithOAuth('discord'));
context.read<SignInBloc>().add(
const SignInEvent.signedInWithOAuth('google'),
);
},
),
// const SizedBox(width: 20),
// ThirdPartySignInButton(
// icon: 'login/github-mark',
// onPressed: () {
// getIt<KeyValueStorage>().set(KVKeys.loginType, 'supabase');
// context
// .read<SignInBloc>()
// .add(const SignInEvent.signedInWithOAuth('github'));
// },
// ),
// const SizedBox(width: 20),
// ThirdPartySignInButton(
// icon: 'login/discord-mark',
// onPressed: () {
// getIt<KeyValueStorage>().set(KVKeys.loginType, 'supabase');
// context
// .read<SignInBloc>()
// .add(const SignInEvent.signedInWithOAuth('discord'));
// },
// ),
],
);
}
@@ -85,7 +85,6 @@ class _SkipLogInScreenState extends State<SkipLogInScreen> {
),
),
const Spacer(),
const VSpace(48),
const SkipLoginPageFooter(),
const VSpace(20),
],
@@ -172,36 +171,48 @@ class SubscribeButtons extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
return Wrap(
alignment: WrapAlignment.center,
children: [
FlowyText.regular(
LocaleKeys.youCanAlso.tr(),
fontSize: FontSizes.s12,
Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
FlowyText.regular(
LocaleKeys.youCanAlso.tr(),
fontSize: FontSizes.s12,
),
FlowyTextButton(
LocaleKeys.githubStarText.tr(),
padding: const EdgeInsets.symmetric(horizontal: 4),
fontWeight: FontWeight.w500,
fontColor: Theme.of(context).colorScheme.primary,
hoverColor: Colors.transparent,
fillColor: Colors.transparent,
onPressed: () => _launchURL(
'https://github.com/AppFlowy-IO/appflowy',
),
),
],
),
FlowyTextButton(
LocaleKeys.githubStarText.tr(),
fontWeight: FontWeight.w500,
fontColor: Theme.of(context).colorScheme.primary,
hoverColor: Colors.transparent,
fillColor: Colors.transparent,
onPressed: () => _launchURL(
'https://github.com/AppFlowy-IO/appflowy',
),
),
FlowyText.regular(
LocaleKeys.and.tr(),
fontSize: FontSizes.s12,
),
FlowyTextButton(
LocaleKeys.subscribeNewsletterText.tr(),
overflow: TextOverflow.ellipsis,
fontWeight: FontWeight.w500,
fontColor: Theme.of(context).colorScheme.primary,
hoverColor: Colors.transparent,
fillColor: Colors.transparent,
onPressed: () => _launchURL('https://www.appflowy.io/blog'),
Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
FlowyText.regular(
LocaleKeys.and.tr(),
fontSize: FontSizes.s12,
),
FlowyTextButton(
LocaleKeys.subscribeNewsletterText.tr(),
padding: const EdgeInsets.symmetric(horizontal: 4.0),
fontWeight: FontWeight.w500,
fontColor: Theme.of(context).colorScheme.primary,
hoverColor: Colors.transparent,
fillColor: Colors.transparent,
onPressed: () => _launchURL('https://www.appflowy.io/blog'),
),
],
),
],
);
@@ -224,43 +235,44 @@ class LanguageSelectorOnWelcomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<AppearanceSettingsCubit, AppearanceSettingsState>(
builder: (context, state) {
return AppFlowyPopover(
offset: const Offset(0, -450),
direction: PopoverDirection.bottomWithRightAligned,
child: FlowyButton(
useIntrinsicWidth: true,
text: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: [
const FlowySvg(
name: 'login/language',
size: Size.square(20),
),
const HSpace(4),
FlowyText(
languageFromLocale(state.locale),
),
// const HSpace(4),
const FlowySvg(
name: 'home/drop_down_hide',
size: Size.square(20),
),
],
return AppFlowyPopover(
offset: const Offset(0, -450),
direction: PopoverDirection.bottomWithRightAligned,
child: FlowyButton(
useIntrinsicWidth: true,
text: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: [
const FlowySvg(
name: 'login/language',
size: Size.square(20),
),
),
popupBuilder: (BuildContext context) {
final easyLocalization = EasyLocalization.of(context);
if (easyLocalization == null) {
return const SizedBox.shrink();
}
final allLocales = easyLocalization.supportedLocales;
return LanguageItemsListView(
allLocales: allLocales,
);
},
const HSpace(4),
Builder(
builder: (context) {
final currentLocale =
context.watch<AppearanceSettingsCubit>().state.locale;
return FlowyText(
languageFromLocale(currentLocale),
);
},
),
const FlowySvg(
name: 'home/drop_down_hide',
size: Size.square(20),
),
],
),
),
popupBuilder: (BuildContext context) {
final easyLocalization = EasyLocalization.of(context);
if (easyLocalization == null) {
return const SizedBox.shrink();
}
final allLocales = easyLocalization.supportedLocales;
return LanguageItemsListView(
allLocales: allLocales,
);
},
);
@@ -88,7 +88,7 @@ class SplashScreen extends StatelessWidget {
void _handleUnauthenticated(BuildContext context, Unauthenticated result) {
// if the env is not configured, we will skip to the 'skip login screen'.
if (isSupabaseEnable) {
if (isSupabaseEnabled) {
getIt<SplashRoute>().pushSignInScreen(context);
} else {
getIt<SplashRoute>().pushSkipLoginScreen(context);
Loaded 100 of 490 files, more files were not shown because too many files have changed in this diff. Show more