diff --git a/.github/workflows/tauri2_ci.yaml b/.github/workflows/tauri2_ci.yaml
index 5bf8cbb09f..6bbb7928ee 100644
--- a/.github/workflows/tauri2_ci.yaml
+++ b/.github/workflows/tauri2_ci.yaml
@@ -20,34 +20,34 @@ concurrency:
cancel-in-progress: true
jobs:
- tauri-build-self-hosted:
- if: github.event.pull_request.head.repo.full_name == github.repository
- runs-on: self-hosted
-
- steps:
- - uses: actions/checkout@v4
- - name: install frontend dependencies
- working-directory: frontend/appflowy_web_app
- run: |
- mkdir dist
- pnpm install
- cd src-tauri && cargo build
-
- - name: test and lint
- working-directory: frontend/appflowy_web_app
- run: |
- pnpm run lint:tauri
-
- - uses: tauri-apps/tauri-action@v0
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- tauriScript: pnpm tauri
- projectPath: frontend/appflowy_web_app
- args: "--debug"
+ # tauri-build-self-hosted:
+ # if: github.event.pull_request.head.repo.full_name == github.repository
+ # runs-on: self-hosted
+ #
+ # steps:
+ # - uses: actions/checkout@v4
+ # - name: install frontend dependencies
+ # working-directory: frontend/appflowy_web_app
+ # run: |
+ # mkdir dist
+ # pnpm install
+ # cd src-tauri && cargo build
+ #
+ # - name: test and lint
+ # working-directory: frontend/appflowy_web_app
+ # run: |
+ # pnpm run lint:tauri
+ #
+ # - uses: tauri-apps/tauri-action@v0
+ # env:
+ # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ # with:
+ # tauriScript: pnpm tauri
+ # projectPath: frontend/appflowy_web_app
+ # args: "--debug"
tauri-build-ubuntu:
- if: github.event.pull_request.head.repo.full_name != github.repository
+ #if: github.event.pull_request.head.repo.full_name != github.repository
runs-on: ubuntu-20.04
steps:
diff --git a/frontend/appflowy_flutter/android/app/src/main/ic_launcher-playstore.png b/frontend/appflowy_flutter/android/app/src/main/ic_launcher-playstore.png
new file mode 100644
index 0000000000..c691e14bdc
Binary files /dev/null and b/frontend/appflowy_flutter/android/app/src/main/ic_launcher-playstore.png differ
diff --git a/frontend/appflowy_flutter/android/app/src/main/res/drawable/launch_background.xml b/frontend/appflowy_flutter/android/app/src/main/res/drawable/launcher_background.xml
similarity index 100%
rename from frontend/appflowy_flutter/android/app/src/main/res/drawable/launch_background.xml
rename to frontend/appflowy_flutter/android/app/src/main/res/drawable/launcher_background.xml
diff --git a/frontend/appflowy_flutter/android/app/src/main/res/drawable/launcher_foreground.xml b/frontend/appflowy_flutter/android/app/src/main/res/drawable/launcher_foreground.xml
new file mode 100644
index 0000000000..c7ec6fdd6f
--- /dev/null
+++ b/frontend/appflowy_flutter/android/app/src/main/res/drawable/launcher_foreground.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/appflowy_flutter/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000000..ba42ab6878
--- /dev/null
+++ b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/appflowy_flutter/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000000..036d09bc5f
--- /dev/null
+++ b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/appflowy_flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
index b00c03fd17..911ee844c7 100644
Binary files a/frontend/appflowy_flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/frontend/appflowy_flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000000..1b466c0eb2
Binary files /dev/null and b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/frontend/appflowy_flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png
new file mode 100644
index 0000000000..56ea852799
Binary files /dev/null and b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png differ
diff --git a/frontend/appflowy_flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..f4d14c0d60
Binary files /dev/null and b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/frontend/appflowy_flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
index e76d95c5be..fe7a94797a 100644
Binary files a/frontend/appflowy_flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/frontend/appflowy_flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000000..15fb3c4ddf
Binary files /dev/null and b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/frontend/appflowy_flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png
new file mode 100644
index 0000000000..63fa775f58
Binary files /dev/null and b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png differ
diff --git a/frontend/appflowy_flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..fda3c7fa3e
Binary files /dev/null and b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
index c5188d2de4..61e49810e8 100644
Binary files a/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000000..132a0e9ff0
Binary files /dev/null and b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png
new file mode 100644
index 0000000000..f9e393537d
Binary files /dev/null and b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png differ
diff --git a/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..8efe0ff281
Binary files /dev/null and b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
index 3cc1a254c9..be4cf46069 100644
Binary files a/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000000..95a312fbc5
Binary files /dev/null and b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png
new file mode 100644
index 0000000000..a63acece70
Binary files /dev/null and b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png differ
diff --git a/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..727cb0c58a
Binary files /dev/null and b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
index c8f21cf1b3..c9e8059fe3 100644
Binary files a/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000000..d5ce932756
Binary files /dev/null and b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png
new file mode 100644
index 0000000000..ad1543e064
Binary files /dev/null and b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png differ
diff --git a/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..010733d23d
Binary files /dev/null and b/frontend/appflowy_flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/frontend/appflowy_flutter/android/app/src/main/res/values/ic_launcher_background.xml b/frontend/appflowy_flutter/android/app/src/main/res/values/ic_launcher_background.xml
new file mode 100644
index 0000000000..c5d5899fdf
--- /dev/null
+++ b/frontend/appflowy_flutter/android/app/src/main/res/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+
+
+ #FFFFFF
+
\ No newline at end of file
diff --git a/frontend/appflowy_flutter/dart_dependency_validator.yaml b/frontend/appflowy_flutter/dart_dependency_validator.yaml
new file mode 100644
index 0000000000..cb1df68bb6
--- /dev/null
+++ b/frontend/appflowy_flutter/dart_dependency_validator.yaml
@@ -0,0 +1,12 @@
+# dart_dependency_validator.yaml
+
+allow_pins: true
+
+include:
+ - "lib/**"
+
+exclude:
+ - "packages/**"
+
+ignore:
+ - analyzer
diff --git a/frontend/appflowy_flutter/integration_test/cloud/supabase_auth_test.dart b/frontend/appflowy_flutter/integration_test/cloud/supabase_auth_test.dart
index 15c9c3c347..71cbc11431 100644
--- a/frontend/appflowy_flutter/integration_test/cloud/supabase_auth_test.dart
+++ b/frontend/appflowy_flutter/integration_test/cloud/supabase_auth_test.dart
@@ -1,93 +1,93 @@
-import 'package:appflowy/env/cloud_env.dart';
-import 'package:appflowy/workspace/application/settings/prelude.dart';
-import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.dart';
-import 'package:appflowy/workspace/presentation/settings/widgets/setting_supabase_cloud.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:integration_test/integration_test.dart';
+// import 'package:appflowy/env/cloud_env.dart';
+// import 'package:appflowy/workspace/application/settings/prelude.dart';
+// import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.dart';
+// import 'package:appflowy/workspace/presentation/settings/widgets/setting_supabase_cloud.dart';
+// import 'package:flutter_test/flutter_test.dart';
+// import 'package:integration_test/integration_test.dart';
-import '../shared/util.dart';
+// import '../shared/util.dart';
-void main() {
- IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+// void main() {
+// IntegrationTestWidgetsFlutterBinding.ensureInitialized();
- group('supabase auth', () {
- testWidgets('sign in with supabase', (tester) async {
- await tester.initializeAppFlowy(cloudType: AuthenticatorType.supabase);
- await tester.tapGoogleLoginInButton();
- await tester.expectToSeeHomePageWithGetStartedPage();
- });
+// group('supabase auth', () {
+// testWidgets('sign in with supabase', (tester) async {
+// await tester.initializeAppFlowy(cloudType: AuthenticatorType.supabase);
+// await tester.tapGoogleLoginInButton();
+// await tester.expectToSeeHomePageWithGetStartedPage();
+// });
- testWidgets('sign out with supabase', (tester) async {
- await tester.initializeAppFlowy(cloudType: AuthenticatorType.supabase);
- await tester.tapGoogleLoginInButton();
+// testWidgets('sign out with supabase', (tester) async {
+// await tester.initializeAppFlowy(cloudType: AuthenticatorType.supabase);
+// await tester.tapGoogleLoginInButton();
- // Open the setting page and sign out
- await tester.openSettings();
- await tester.openSettingsPage(SettingsPage.account);
- await tester.logout();
+// // Open the setting page and sign out
+// await tester.openSettings();
+// await tester.openSettingsPage(SettingsPage.account);
+// await tester.logout();
- // Go to the sign in page again
- await tester.pumpAndSettle(const Duration(seconds: 1));
- tester.expectToSeeGoogleLoginButton();
- });
+// // Go to the sign in page again
+// await tester.pumpAndSettle(const Duration(seconds: 1));
+// tester.expectToSeeGoogleLoginButton();
+// });
- testWidgets('sign in as anonymous', (tester) async {
- await tester.initializeAppFlowy(cloudType: AuthenticatorType.supabase);
- await tester.tapSignInAsGuest();
+// testWidgets('sign in as anonymous', (tester) async {
+// await tester.initializeAppFlowy(cloudType: AuthenticatorType.supabase);
+// await tester.tapSignInAsGuest();
- // should not see the sync setting page when sign in as anonymous
- await tester.openSettings();
- await tester.openSettingsPage(SettingsPage.account);
+// // should not see the sync setting page when sign in as anonymous
+// await tester.openSettings();
+// await tester.openSettingsPage(SettingsPage.account);
- // Scroll to sign-out
- await tester.scrollUntilVisible(
- find.byType(SignInOutButton),
- 100,
- scrollable: find.findSettingsScrollable(),
- );
- await tester.tapButton(find.byType(SignInOutButton));
+// // Scroll to sign-out
+// await tester.scrollUntilVisible(
+// find.byType(SignInOutButton),
+// 100,
+// scrollable: find.findSettingsScrollable(),
+// );
+// await tester.tapButton(find.byType(SignInOutButton));
- tester.expectToSeeGoogleLoginButton();
- });
+// tester.expectToSeeGoogleLoginButton();
+// });
- // testWidgets('enable encryption', (tester) async {
- // await tester.initializeAppFlowy(cloudType: CloudType.supabase);
- // await tester.tapGoogleLoginInButton();
+// // testWidgets('enable encryption', (tester) async {
+// // await tester.initializeAppFlowy(cloudType: CloudType.supabase);
+// // await tester.tapGoogleLoginInButton();
- // // Open the setting page and sign out
- // await tester.openSettings();
- // await tester.openSettingsPage(SettingsPage.cloud);
+// // // Open the setting page and sign out
+// // await tester.openSettings();
+// // await tester.openSettingsPage(SettingsPage.cloud);
- // // the switch should be off by default
- // tester.assertEnableEncryptSwitchValue(false);
- // await tester.toggleEnableEncrypt();
+// // // the switch should be off by default
+// // tester.assertEnableEncryptSwitchValue(false);
+// // await tester.toggleEnableEncrypt();
- // // the switch should be on after toggling
- // tester.assertEnableEncryptSwitchValue(true);
+// // // the switch should be on after toggling
+// // tester.assertEnableEncryptSwitchValue(true);
- // // the switch can not be toggled back to off
- // await tester.toggleEnableEncrypt();
- // tester.assertEnableEncryptSwitchValue(true);
- // });
+// // // the switch can not be toggled back to off
+// // await tester.toggleEnableEncrypt();
+// // tester.assertEnableEncryptSwitchValue(true);
+// // });
- testWidgets('enable sync', (tester) async {
- await tester.initializeAppFlowy(cloudType: AuthenticatorType.supabase);
- await tester.tapGoogleLoginInButton();
+// testWidgets('enable sync', (tester) async {
+// await tester.initializeAppFlowy(cloudType: AuthenticatorType.supabase);
+// await tester.tapGoogleLoginInButton();
- // Open the setting page and sign out
- await tester.openSettings();
- await tester.openSettingsPage(SettingsPage.cloud);
+// // Open the setting page and sign out
+// await tester.openSettings();
+// await tester.openSettingsPage(SettingsPage.cloud);
- // the switch should be on by default
- tester.assertSupabaseEnableSyncSwitchValue(true);
- await tester.toggleEnableSync(SupabaseEnableSync);
+// // the switch should be on by default
+// tester.assertSupabaseEnableSyncSwitchValue(true);
+// await tester.toggleEnableSync(SupabaseEnableSync);
- // the switch should be off
- tester.assertSupabaseEnableSyncSwitchValue(false);
+// // the switch should be off
+// tester.assertSupabaseEnableSyncSwitchValue(false);
- // the switch should be on after toggling
- await tester.toggleEnableSync(SupabaseEnableSync);
- tester.assertSupabaseEnableSyncSwitchValue(true);
- });
- });
-}
+// // the switch should be on after toggling
+// await tester.toggleEnableSync(SupabaseEnableSync);
+// tester.assertSupabaseEnableSyncSwitchValue(true);
+// });
+// });
+// }
diff --git a/frontend/appflowy_flutter/integration_test/cloud/user_setting_sync_test.dart b/frontend/appflowy_flutter/integration_test/cloud/user_setting_sync_test.dart
index 8bd9cffc03..d0377908c3 100644
--- a/frontend/appflowy_flutter/integration_test/cloud/user_setting_sync_test.dart
+++ b/frontend/appflowy_flutter/integration_test/cloud/user_setting_sync_test.dart
@@ -47,31 +47,28 @@ void main() {
await tester.openSettingsPage(SettingsPage.account);
await tester.enterUserName(name);
- await tester.tapEscButton();
-
- // wait 2 seconds for the sync to finish
await tester.pumpAndSettle(const Duration(seconds: 6));
- });
+ await tester.logout();
-
- testWidgets('get user icon and name from server', (tester) async {
- await tester.initializeAppFlowy(
- cloudType: AuthenticatorType.appflowyCloudSelfHost,
- email: email,
- );
- await tester.tapGoogleLoginInButton();
- await tester.expectToSeeHomePageWithGetStartedPage();
- await tester.pumpAndSettle();
-
- await tester.openSettings();
- await tester.openSettingsPage(SettingsPage.account);
-
- // Verify name
- final profileSetting =
- tester.widget(find.byType(UserProfileSetting)) as UserProfileSetting;
-
- expect(profileSetting.name, name);
+ await tester.pumpAndSettle(const Duration(seconds: 2));
});
});
+ testWidgets('get user icon and name from server', (tester) async {
+ await tester.initializeAppFlowy(
+ cloudType: AuthenticatorType.appflowyCloudSelfHost,
+ email: email,
+ );
+ await tester.tapGoogleLoginInButton();
+ await tester.expectToSeeHomePageWithGetStartedPage();
+ await tester.pumpAndSettle();
+ await tester.openSettings();
+ await tester.openSettingsPage(SettingsPage.account);
+
+ // Verify name
+ final profileSetting =
+ tester.widget(find.byType(UserProfileSetting)) as UserProfileSetting;
+
+ expect(profileSetting.name, name);
+ });
}
diff --git a/frontend/appflowy_flutter/integration_test/desktop/database/database_cell_test.dart b/frontend/appflowy_flutter/integration_test/desktop/database/database_cell_test.dart
index cb8338fbb6..66ef6cfd95 100644
--- a/frontend/appflowy_flutter/integration_test/desktop/database/database_cell_test.dart
+++ b/frontend/appflowy_flutter/integration_test/desktop/database/database_cell_test.dart
@@ -461,22 +461,22 @@ void main() {
tester.assertChecklistEditorVisible(visible: true);
// create a new task with enter
- await tester.createNewChecklistTask(name: "task 0", enter: true);
+ await tester.createNewChecklistTask(name: "task 1", enter: true);
// assert that the task is displayed
tester.assertChecklistTaskInEditor(
index: 0,
- name: "task 0",
+ name: "task 1",
isChecked: false,
);
// update the task's name
- await tester.renameChecklistTask(index: 0, name: "task 1");
+ await tester.renameChecklistTask(index: 0, name: "task 11");
// assert that the task's name is updated
tester.assertChecklistTaskInEditor(
index: 0,
- name: "task 1",
+ name: "task 11",
isChecked: false,
);
diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_database_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_database_test.dart
index 0ee7610b93..eb07a2e7a8 100644
--- a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_database_test.dart
+++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_database_test.dart
@@ -176,6 +176,7 @@ Future createInlineDatabase(
await tester.editor.showSlashMenu();
await tester.editor.tapSlashMenuItemWithName(
layout.slashMenuName,
+ offset: 100,
);
await tester.pumpAndSettle();
diff --git a/frontend/appflowy_flutter/integration_test/shared/auth_operation.dart b/frontend/appflowy_flutter/integration_test/shared/auth_operation.dart
index 56815714c0..e01e02c6e1 100644
--- a/frontend/appflowy_flutter/integration_test/shared/auth_operation.dart
+++ b/frontend/appflowy_flutter/integration_test/shared/auth_operation.dart
@@ -2,7 +2,6 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
-import 'package:appflowy/workspace/presentation/settings/widgets/setting_supabase_cloud.dart';
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
@@ -52,26 +51,6 @@ extension AppFlowyAuthTest on WidgetTester {
assert(isSwitched == value);
}
- void assertEnableEncryptSwitchValue(bool value) {
- assertSwitchValue(
- find.descendant(
- of: find.byType(EnableEncrypt),
- matching: find.byWidgetPredicate((widget) => widget is Switch),
- ),
- value,
- );
- }
-
- void assertSupabaseEnableSyncSwitchValue(bool value) {
- assertSwitchValue(
- find.descendant(
- of: find.byType(SupabaseEnableSync),
- matching: find.byWidgetPredicate((widget) => widget is Switch),
- ),
- value,
- );
- }
-
void assertAppFlowyCloudEnableSyncSwitchValue(bool value) {
assertToggleValue(
find.descendant(
@@ -82,15 +61,6 @@ extension AppFlowyAuthTest on WidgetTester {
);
}
- Future toggleEnableEncrypt() async {
- final finder = find.descendant(
- of: find.byType(EnableEncrypt),
- matching: find.byWidgetPredicate((widget) => widget is Switch),
- );
-
- await tapButton(finder);
- }
-
Future toggleEnableSync(Type syncButton) async {
final finder = find.descendant(
of: find.byType(syncButton),
diff --git a/frontend/appflowy_flutter/integration_test/shared/base.dart b/frontend/appflowy_flutter/integration_test/shared/base.dart
index 16a576154f..371cd9b839 100644
--- a/frontend/appflowy_flutter/integration_test/shared/base.dart
+++ b/frontend/appflowy_flutter/integration_test/shared/base.dart
@@ -7,7 +7,6 @@ import 'package:appflowy/startup/entry_point.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
-import 'package:appflowy/user/application/auth/supabase_mock_auth_service.dart';
import 'package:appflowy/user/presentation/presentation.dart';
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart';
@@ -55,8 +54,6 @@ extension AppFlowyTestBase on WidgetTester {
switch (cloudType) {
case AuthenticatorType.local:
break;
- case AuthenticatorType.supabase:
- break;
case AuthenticatorType.appflowyCloudSelfHost:
rustEnvs["GOTRUE_ADMIN_EMAIL"] = "admin@example.com";
rustEnvs["GOTRUE_ADMIN_PASSWORD"] = "password";
@@ -75,13 +72,6 @@ extension AppFlowyTestBase on WidgetTester {
case AuthenticatorType.local:
await useLocalServer();
break;
- case AuthenticatorType.supabase:
- await useTestSupabaseCloud();
- getIt.unregister();
- getIt.registerFactory(
- () => SupabaseMockAuthService(),
- );
- break;
case AuthenticatorType.appflowyCloudSelfHost:
await useTestSelfHostedAppFlowyCloud();
getIt.unregister();
@@ -242,13 +232,6 @@ extension AppFlowyFinderTestBase on CommonFinders {
}
}
-Future useTestSupabaseCloud() async {
- await useSupabaseCloud(
- url: TestEnv.supabaseUrl,
- anonKey: TestEnv.supabaseAnonKey,
- );
-}
-
Future useTestSelfHostedAppFlowyCloud() async {
await useSelfHostedAppFlowyCloudWithURL(TestEnv.afCloudUrl);
}
diff --git a/frontend/appflowy_flutter/integration_test/shared/settings.dart b/frontend/appflowy_flutter/integration_test/shared/settings.dart
index 9dec3209a4..20193dfd9b 100644
--- a/frontend/appflowy_flutter/integration_test/shared/settings.dart
+++ b/frontend/appflowy_flutter/integration_test/shared/settings.dart
@@ -80,7 +80,7 @@ extension AppFlowySettings on WidgetTester {
of: find.byType(UserProfileSetting),
matching: find.byFlowySvg(FlowySvgs.edit_s),
);
- await tap(editUsernameFinder);
+ await tap(editUsernameFinder, warnIfMissed: false);
await pumpAndSettle();
final userNameFinder = find.descendant(
diff --git a/frontend/appflowy_flutter/ios/Podfile.lock b/frontend/appflowy_flutter/ios/Podfile.lock
index d7647a9d4a..af96ce7ccb 100644
--- a/frontend/appflowy_flutter/ios/Podfile.lock
+++ b/frontend/appflowy_flutter/ios/Podfile.lock
@@ -63,12 +63,15 @@ PODS:
- FlutterMacOS
- permission_handler_apple (9.3.0):
- Flutter
- - printing (1.0.0):
- - Flutter
- ReachabilitySwift (5.0.0)
- SDWebImage (5.14.2):
- SDWebImage/Core (= 5.14.2)
- SDWebImage/Core (5.14.2)
+ - Sentry/HybridSDK (8.33.0)
+ - sentry_flutter (8.7.0):
+ - Flutter
+ - FlutterMacOS
+ - Sentry/HybridSDK (= 8.33.0)
- share_plus (0.0.1):
- Flutter
- shared_preferences_foundation (0.0.1):
@@ -100,7 +103,7 @@ DEPENDENCIES:
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- - printing (from `.symlinks/plugins/printing/ios`)
+ - sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite (from `.symlinks/plugins/sqflite/darwin`)
@@ -113,6 +116,7 @@ SPEC REPOS:
- DKPhotoGallery
- ReachabilitySwift
- SDWebImage
+ - Sentry
- SwiftyGif
- Toast
@@ -147,8 +151,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
- printing:
- :path: ".symlinks/plugins/printing/ios"
+ sentry_flutter:
+ :path: ".symlinks/plugins/sentry_flutter/ios"
share_plus:
:path: ".symlinks/plugins/share_plus/ios"
shared_preferences_foundation:
@@ -170,7 +174,7 @@ SPEC CHECKSUMS:
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
flowy_infra_ui: 0455e1fa8c51885aa1437848e361e99419f34ebc
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
- fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c
+ fluttertoast: 723e187574b149e68e63ca4d39b837586b903cfa
image_picker_ios: 99dfe1854b4fa34d0364e74a78448a0151025425
integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4
irondash_engine_context: 3458bf979b90d616ffb8ae03a150bafe2e860cc9
@@ -178,9 +182,10 @@ SPEC CHECKSUMS:
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
- printing: 233e1b73bd1f4a05615548e9b5a324c98588640b
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
SDWebImage: b9a731e1d6307f44ca703b3976d18c24ca561e84
+ Sentry: 8560050221424aef0bebc8e31eedf00af80f90a6
+ sentry_flutter: e26b861f744e5037a3faf9bf56603ec65d658a61
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
@@ -191,4 +196,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: d0d9b4ff572d8695c38eb3f9b490f55cdfc57eca
-COCOAPODS: 1.15.2
+COCOAPODS: 1.11.3
diff --git a/frontend/appflowy_flutter/lib/env/backend_env.dart b/frontend/appflowy_flutter/lib/env/backend_env.dart
index fa0bf575a3..f8aa715a40 100644
--- a/frontend/appflowy_flutter/lib/env/backend_env.dart
+++ b/frontend/appflowy_flutter/lib/env/backend_env.dart
@@ -13,7 +13,6 @@ class AppFlowyConfiguration {
required this.device_id,
required this.platform,
required this.authenticator_type,
- required this.supabase_config,
required this.appflowy_cloud_config,
required this.envs,
});
@@ -28,41 +27,12 @@ class AppFlowyConfiguration {
final String device_id;
final String platform;
final int authenticator_type;
- final SupabaseConfiguration supabase_config;
final AppFlowyCloudConfiguration appflowy_cloud_config;
final Map envs;
Map toJson() => _$AppFlowyConfigurationToJson(this);
}
-@JsonSerializable()
-class SupabaseConfiguration {
- SupabaseConfiguration({
- required this.url,
- required this.anon_key,
- });
-
- factory SupabaseConfiguration.fromJson(Map json) =>
- _$SupabaseConfigurationFromJson(json);
-
- /// Indicates whether the sync feature is enabled.
- final String url;
- final String anon_key;
-
- Map toJson() => _$SupabaseConfigurationToJson(this);
-
- static SupabaseConfiguration defaultConfig() {
- return SupabaseConfiguration(
- url: '',
- anon_key: '',
- );
- }
-
- bool get isValid {
- return url.isNotEmpty && anon_key.isNotEmpty;
- }
-}
-
@JsonSerializable()
class AppFlowyCloudConfiguration {
AppFlowyCloudConfiguration({
diff --git a/frontend/appflowy_flutter/lib/env/cloud_env.dart b/frontend/appflowy_flutter/lib/env/cloud_env.dart
index 9e8ea0d4f9..fcad1a1f2f 100644
--- a/frontend/appflowy_flutter/lib/env/cloud_env.dart
+++ b/frontend/appflowy_flutter/lib/env/cloud_env.dart
@@ -21,9 +21,6 @@ Future _setAuthenticatorType(AuthenticatorType ty) async {
case AuthenticatorType.local:
await getIt().set(KVKeys.kCloudType, 0.toString());
break;
- case AuthenticatorType.supabase:
- await getIt().set(KVKeys.kCloudType, 1.toString());
- break;
case AuthenticatorType.appflowyCloud:
await getIt().set(KVKeys.kCloudType, 2.toString());
break;
@@ -63,8 +60,6 @@ Future getAuthenticatorType() async {
switch (value ?? "0") {
case "0":
return AuthenticatorType.local;
- case "1":
- return AuthenticatorType.supabase;
case "2":
return AuthenticatorType.appflowyCloud;
case "3":
@@ -93,10 +88,6 @@ Future getAuthenticatorType() async {
/// Returns `false` otherwise.
bool get isAuthEnabled {
final env = getIt();
- if (env.authenticatorType == AuthenticatorType.supabase) {
- return env.supabaseConfig.isValid;
- }
-
if (env.authenticatorType.isAppFlowyCloudEnabled) {
return env.appflowyCloudConfig.isValid;
}
@@ -104,19 +95,6 @@ bool get isAuthEnabled {
return false;
}
-/// Checks if Supabase is enabled.
-///
-/// This getter evaluates if Supabase should be enabled based on the
-/// current integration mode and cloud type setting.
-///
-/// Returns:
-/// A boolean value indicating whether Supabase is enabled. It returns `true`
-/// if the application is in release or develop mode and the current cloud type
-/// is `CloudType.supabase`. Otherwise, it returns `false`.
-bool get isSupabaseEnabled {
- return currentCloudType().isSupabaseEnabled;
-}
-
/// Determines if AppFlowy Cloud is enabled.
bool get isAppFlowyCloudEnabled {
return currentCloudType().isAppFlowyCloudEnabled;
@@ -124,7 +102,6 @@ bool get isAppFlowyCloudEnabled {
enum AuthenticatorType {
local,
- supabase,
appflowyCloud,
appflowyCloudSelfHost,
// The 'appflowyCloudDevelop' type is used for develop purposes only.
@@ -137,14 +114,10 @@ enum AuthenticatorType {
this == AuthenticatorType.appflowyCloudDevelop ||
this == AuthenticatorType.appflowyCloud;
- bool get isSupabaseEnabled => this == AuthenticatorType.supabase;
-
int get value {
switch (this) {
case AuthenticatorType.local:
return 0;
- case AuthenticatorType.supabase:
- return 1;
case AuthenticatorType.appflowyCloud:
return 2;
case AuthenticatorType.appflowyCloudSelfHost:
@@ -158,8 +131,6 @@ enum AuthenticatorType {
switch (value) {
case 0:
return AuthenticatorType.local;
- case 1:
- return AuthenticatorType.supabase;
case 2:
return AuthenticatorType.appflowyCloud;
case 3:
@@ -197,25 +168,15 @@ Future useLocalServer() async {
await _setAuthenticatorType(AuthenticatorType.local);
}
-Future useSupabaseCloud({
- required String url,
- required String anonKey,
-}) async {
- await _setAuthenticatorType(AuthenticatorType.supabase);
- await setSupabaseServer(url, anonKey);
-}
-
/// Use getIt() to get the shared environment.
class AppFlowyCloudSharedEnv {
AppFlowyCloudSharedEnv({
required AuthenticatorType authenticatorType,
required this.appflowyCloudConfig,
- required this.supabaseConfig,
}) : _authenticatorType = authenticatorType;
final AuthenticatorType _authenticatorType;
final AppFlowyCloudConfiguration appflowyCloudConfig;
- final SupabaseConfiguration supabaseConfig;
AuthenticatorType get authenticatorType => _authenticatorType;
@@ -229,10 +190,6 @@ class AppFlowyCloudSharedEnv {
? await getAppFlowyCloudConfig(authenticatorType)
: AppFlowyCloudConfiguration.defaultConfig();
- final supabaseCloudConfig = authenticatorType.isSupabaseEnabled
- ? await getSupabaseCloudConfig()
- : SupabaseConfiguration.defaultConfig();
-
// In the backend, the value '2' represents the use of AppFlowy Cloud. However, in the frontend,
// we distinguish between [AuthenticatorType.appflowyCloudSelfHost] and [AuthenticatorType.appflowyCloud].
// When the cloud type is [AuthenticatorType.appflowyCloudSelfHost] in the frontend, it should be
@@ -244,7 +201,6 @@ class AppFlowyCloudSharedEnv {
return AppFlowyCloudSharedEnv(
authenticatorType: authenticatorType,
appflowyCloudConfig: appflowyCloudConfig,
- supabaseConfig: supabaseCloudConfig,
);
} else {
// Using the cloud settings from the .env file.
@@ -257,7 +213,6 @@ class AppFlowyCloudSharedEnv {
return AppFlowyCloudSharedEnv(
authenticatorType: AuthenticatorType.fromValue(Env.authenticatorType),
appflowyCloudConfig: appflowyCloudConfig,
- supabaseConfig: SupabaseConfiguration.defaultConfig(),
);
}
}
@@ -265,8 +220,7 @@ class AppFlowyCloudSharedEnv {
@override
String toString() {
return 'authenticator: $_authenticatorType\n'
- 'appflowy: ${appflowyCloudConfig.toJson()}\n'
- 'supabase: ${supabaseConfig.toJson()})\n';
+ 'appflowy: ${appflowyCloudConfig.toJson()}\n';
}
}
@@ -354,22 +308,3 @@ Future setSupabaseServer(
await getIt().set(KVKeys.kSupabaseAnonKey, anonKey);
}
}
-
-Future getSupabaseCloudConfig() async {
- final url = await _getSupabaseUrl();
- final anonKey = await _getSupabaseAnonKey();
- return SupabaseConfiguration(
- url: url,
- anon_key: anonKey,
- );
-}
-
-Future _getSupabaseUrl() async {
- final result = await getIt().get(KVKeys.kSupabaseURL);
- return result ?? '';
-}
-
-Future _getSupabaseAnonKey() async {
- final result = await getIt().get(KVKeys.kSupabaseAnonKey);
- return result ?? '';
-}
diff --git a/frontend/appflowy_flutter/lib/env/env.dart b/frontend/appflowy_flutter/lib/env/env.dart
index b861b4cfb8..cfd9837944 100644
--- a/frontend/appflowy_flutter/lib/env/env.dart
+++ b/frontend/appflowy_flutter/lib/env/env.dart
@@ -36,4 +36,11 @@ abstract class Env {
defaultValue: '',
)
static const String internalBuild = _Env.internalBuild;
+
+ @EnviedField(
+ obfuscate: false,
+ varName: 'SENTRY_DSN',
+ defaultValue: '',
+ )
+ static const String sentryDsn = _Env.sentryDsn;
}
diff --git a/frontend/appflowy_flutter/lib/mobile/application/mobile_router.dart b/frontend/appflowy_flutter/lib/mobile/application/mobile_router.dart
index 8b9f1e70ff..153ed451be 100644
--- a/frontend/appflowy_flutter/lib/mobile/application/mobile_router.dart
+++ b/frontend/appflowy_flutter/lib/mobile/application/mobile_router.dart
@@ -2,27 +2,41 @@ import 'dart:async';
import 'dart:convert';
import 'package:appflowy/mobile/presentation/chat/mobile_chat_screen.dart';
-import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart';
-import 'package:flutter/material.dart';
-
import 'package:appflowy/mobile/presentation/database/board/mobile_board_screen.dart';
import 'package:appflowy/mobile/presentation/database/mobile_calendar_screen.dart';
import 'package:appflowy/mobile/presentation/database/mobile_grid_screen.dart';
import 'package:appflowy/mobile/presentation/presentation.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/recent/cached_recent_service.dart';
+import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
+import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
extension MobileRouter on BuildContext {
- Future pushView(ViewPB view, [Map? arguments]) async {
+ Future pushView(
+ ViewPB view, {
+ Map? arguments,
+ bool addInRecent = true,
+ bool showMoreButton = true,
+ String? fixedTitle,
+ }) async {
// set the current view before pushing the new view
getIt().latestOpenView = view;
unawaited(getIt().updateRecentViews([view.id], true));
+ final queryParameters = view.queryParameters(arguments);
+
+ if (view.layout == ViewLayoutPB.Document) {
+ queryParameters[MobileDocumentScreen.viewShowMoreButton] =
+ showMoreButton.toString();
+ if (fixedTitle != null) {
+ queryParameters[MobileDocumentScreen.viewFixedTitle] = fixedTitle;
+ }
+ }
final uri = Uri(
path: view.routeName,
- queryParameters: view.queryParameters(arguments),
+ queryParameters: queryParameters,
).toString();
await push(uri);
}
diff --git a/frontend/appflowy_flutter/lib/mobile/application/recent/recent_view_bloc.dart b/frontend/appflowy_flutter/lib/mobile/application/recent/recent_view_bloc.dart
index 547c81f00b..99098f930d 100644
--- a/frontend/appflowy_flutter/lib/mobile/application/recent/recent_view_bloc.dart
+++ b/frontend/appflowy_flutter/lib/mobile/application/recent/recent_view_bloc.dart
@@ -1,7 +1,5 @@
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
-import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart';
import 'package:appflowy/plugins/document/application/document_listener.dart';
-import 'package:appflowy/plugins/document/application/document_service.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/workspace/application/view/prelude.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
@@ -113,7 +111,6 @@ class RecentViewBloc extends Bloc {
);
}
- final _service = DocumentService();
final ViewPB view;
final DocumentListener _documentListener;
final ViewListener _viewListener;
@@ -124,16 +121,6 @@ class RecentViewBloc extends Bloc {
// for the version under 0.5.5
Future<(CoverType, String?)> getCoverV1() async {
- final result = await _service.getDocument(documentId: view.id);
- final document = result.fold((s) => s.toDocument(), (f) => null);
- if (document != null) {
- final coverType = CoverType.fromString(
- document.root.attributes[DocumentHeaderBlockKeys.coverType],
- );
- final coverValue = document
- .root.attributes[DocumentHeaderBlockKeys.coverDetails] as String?;
- return (coverType, coverValue);
- }
return (CoverType.none, null);
}
diff --git a/frontend/appflowy_flutter/lib/mobile/application/user_profile/user_profile_bloc.dart b/frontend/appflowy_flutter/lib/mobile/application/user_profile/user_profile_bloc.dart
index 7edec07cc1..1480cc02e9 100644
--- a/frontend/appflowy_flutter/lib/mobile/application/user_profile/user_profile_bloc.dart
+++ b/frontend/appflowy_flutter/lib/mobile/application/user_profile/user_profile_bloc.dart
@@ -12,12 +12,12 @@ class UserProfileBloc extends Bloc {
UserProfileBloc() : super(const _Initial()) {
on((event, emit) async {
await event.when(
- started: () async => _initalize(emit),
+ started: () async => _initialize(emit),
);
});
}
- Future _initalize(Emitter emit) async {
+ Future _initialize(Emitter emit) async {
emit(const UserProfileState.loading());
final workspaceOrFailure =
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/base/app_bar/app_bar.dart b/frontend/appflowy_flutter/lib/mobile/presentation/base/app_bar/app_bar.dart
index 335f1af489..396ecd6bb8 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/base/app_bar/app_bar.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/base/app_bar/app_bar.dart
@@ -10,7 +10,7 @@ enum FlowyAppBarLeadingType {
Widget getWidget(VoidCallback? onTap) {
switch (this) {
case FlowyAppBarLeadingType.back:
- return AppBarBackButton(onTap: onTap);
+ return AppBarImmersiveBackButton(onTap: onTap);
case FlowyAppBarLeadingType.close:
return AppBarCloseButton(onTap: onTap);
case FlowyAppBarLeadingType.cancel:
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/base/app_bar/app_bar_actions.dart b/frontend/appflowy_flutter/lib/mobile/presentation/base/app_bar/app_bar_actions.dart
index b59c1e68cc..72142d446b 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/base/app_bar/app_bar_actions.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/base/app_bar/app_bar_actions.dart
@@ -26,6 +26,31 @@ class AppBarBackButton extends StatelessWidget {
}
}
+class AppBarImmersiveBackButton extends StatelessWidget {
+ const AppBarImmersiveBackButton({
+ super.key,
+ this.onTap,
+ });
+
+ final VoidCallback? onTap;
+
+ @override
+ Widget build(BuildContext context) {
+ return AppBarButton(
+ onTap: (_) => (onTap ?? () => Navigator.pop(context)).call(),
+ padding: const EdgeInsets.only(
+ left: 12.0,
+ top: 8.0,
+ bottom: 8.0,
+ right: 4.0,
+ ),
+ child: const FlowySvg(
+ FlowySvgs.m_app_bar_back_s,
+ ),
+ );
+ }
+}
+
class AppBarCloseButton extends StatelessWidget {
const AppBarCloseButton({
super.key,
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/base/mobile_view_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/base/mobile_view_page.dart
index 603005fc38..569cdd5fe6 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/base/mobile_view_page.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/base/mobile_view_page.dart
@@ -3,8 +3,8 @@ import 'package:appflowy/mobile/application/base/mobile_view_page_bloc.dart';
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
import 'package:appflowy/mobile/presentation/base/app_bar/app_bar.dart';
import 'package:appflowy/mobile/presentation/base/view_page/app_bar_buttons.dart';
+import 'package:appflowy/mobile/presentation/presentation.dart';
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_state_container.dart';
-import 'package:appflowy/plugins/base/emoji/emoji_text.dart';
import 'package:appflowy/plugins/document/presentation/document_collaborators.dart';
import 'package:appflowy/shared/feature_flags.dart';
import 'package:appflowy/startup/plugin/plugin.dart';
@@ -27,6 +27,8 @@ class MobileViewPage extends StatefulWidget {
required this.viewLayout,
this.title,
this.arguments,
+ this.fixedTitle,
+ this.showMoreButton = true,
});
/// view id
@@ -34,6 +36,10 @@ class MobileViewPage extends StatefulWidget {
final ViewLayoutPB viewLayout;
final String? title;
final Map? arguments;
+ final bool showMoreButton;
+
+ // only used in row page
+ final String? fixedTitle;
@override
State createState() => _MobileViewPageState();
@@ -164,6 +170,9 @@ class _MobileViewPageState extends State {
return plugin.widgetBuilder.buildWidget(
shrinkWrap: false,
context: PluginContext(userProfile: state.userProfilePB),
+ data: {
+ MobileDocumentScreen.viewFixedTitle: widget.fixedTitle,
+ },
);
},
(error) {
@@ -216,13 +225,19 @@ class _MobileViewPageState extends State {
]);
}
- actions.addAll([
- MobileViewPageMoreButton(
- view: view,
- isImmersiveMode: isImmersiveMode,
- appBarOpacity: _appBarOpacity,
- ),
- ]);
+ if (widget.showMoreButton) {
+ actions.addAll([
+ MobileViewPageMoreButton(
+ view: view,
+ isImmersiveMode: isImmersiveMode,
+ appBarOpacity: _appBarOpacity,
+ ),
+ ]);
+ } else {
+ actions.addAll([
+ const HSpace(18.0),
+ ]);
+ }
return actions;
}
@@ -232,19 +247,20 @@ class _MobileViewPageState extends State {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
- if (icon != null && icon.isNotEmpty)
- ConstrainedBox(
- constraints: const BoxConstraints.tightFor(width: 34.0),
- child: EmojiText(
- emoji: '$icon ',
- fontSize: 22.0,
- ),
+ if (icon != null && icon.isNotEmpty) ...[
+ FlowyText.emoji(
+ icon,
+ fontSize: 15.0,
+ figmaLineHeight: 18.0,
),
+ const HSpace(4),
+ ],
Expanded(
child: FlowyText.medium(
- view?.name ?? widget.title ?? '',
+ widget.fixedTitle ?? view?.name ?? widget.title ?? '',
fontSize: 15.0,
overflow: TextOverflow.ellipsis,
+ figmaLineHeight: 18.0,
),
),
],
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_rename_widget.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_rename_widget.dart
index e61f27b6b2..cabc234fec 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_rename_widget.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_rename_widget.dart
@@ -52,6 +52,7 @@ class _MobileBottomSheetRenameWidgetState
height: 42.0,
child: FlowyTextField(
controller: controller,
+ textStyle: Theme.of(context).textTheme.bodyMedium,
keyboardType: TextInputType.text,
onSubmitted: (text) => widget.onRename(text),
),
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item.dart
index b76dc63b1d..c26cf759de 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item.dart
@@ -116,12 +116,18 @@ class _MobileViewItemBottomSheetState extends State {
Future _showConfirmDialog({required VoidCallback onDelete}) async {
await showFlowyCupertinoConfirmDialog(
title: LocaleKeys.sideBar_removePageFromRecent.tr(),
- leftButton: FlowyText.regular(
+ leftButton: FlowyText(
LocaleKeys.button_cancel.tr(),
- color: const Color(0xFF1456F0),
+ fontSize: 17.0,
+ figmaLineHeight: 24.0,
+ fontWeight: FontWeight.w500,
+ color: const Color(0xFF007AFF),
),
- rightButton: FlowyText.medium(
+ rightButton: FlowyText(
LocaleKeys.button_delete.tr(),
+ fontSize: 17.0,
+ figmaLineHeight: 24.0,
+ fontWeight: FontWeight.w400,
color: const Color(0xFFFE0220),
),
onRightButtonPressed: (context) {
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart
index c65f899c34..6a54646301 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart
@@ -3,6 +3,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/base/app_bar/app_bar.dart';
import 'package:appflowy/mobile/presentation/base/app_bar/app_bar_actions.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
+import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/row_page_button.dart';
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart';
import 'package:appflowy/plugins/database/application/cell/bloc/text_cell_bloc.dart';
import 'package:appflowy/plugins/database/application/cell/cell_controller.dart';
@@ -294,6 +295,7 @@ class MobileRowDetailPageContentState
RowCache get rowCache => widget.databaseController.rowCache;
FieldController get fieldController =>
widget.databaseController.fieldController;
+ ValueNotifier primaryFieldId = ValueNotifier('');
@override
void initState() {
@@ -326,7 +328,13 @@ class MobileRowDetailPageContentState
fieldController: fieldController,
rowMeta: rowController.rowMeta,
)..add(const RowBannerEvent.initial()),
- child: BlocBuilder(
+ child: BlocConsumer(
+ listener: (context, state) {
+ if (state.primaryField == null) {
+ return;
+ }
+ primaryFieldId.value = state.primaryField!.id;
+ },
builder: (context, state) {
if (state.primaryField == null) {
return const SizedBox.shrink();
@@ -366,6 +374,23 @@ class MobileRowDetailPageContentState
if (rowDetailState.numHiddenFields != 0) ...[
const ToggleHiddenFieldsVisibilityButton(),
],
+ const VSpace(8.0),
+ ValueListenableBuilder(
+ valueListenable: primaryFieldId,
+ builder: (context, primaryFieldId, child) {
+ if (primaryFieldId.isEmpty) {
+ return const SizedBox.shrink();
+ }
+ return OpenRowPageButton(
+ databaseController: widget.databaseController,
+ cellContext: CellContext(
+ rowId: rowController.rowId,
+ fieldId: primaryFieldId,
+ ),
+ documentId: rowController.rowMeta.documentId,
+ );
+ },
+ ),
MobileRowDetailCreateFieldButton(
viewId: viewId,
fieldController: fieldController,
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/mobile_create_field_button.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/mobile_create_field_button.dart
index d683a9b72d..1d3d3efcf5 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/mobile_create_field_button.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/mobile_create_field_button.dart
@@ -22,7 +22,7 @@ class MobileRowDetailCreateFieldButton extends StatelessWidget {
return ConstrainedBox(
constraints: BoxConstraints(
minWidth: double.infinity,
- minHeight: GridSize.headerHeight,
+ maxHeight: GridSize.headerHeight,
),
child: TextButton.icon(
style: Theme.of(context).textButtonTheme.style?.copyWith(
@@ -37,7 +37,7 @@ class MobileRowDetailCreateFieldButton extends StatelessWidget {
alignment: AlignmentDirectional.centerStart,
splashFactory: NoSplash.splashFactory,
padding: const WidgetStatePropertyAll(
- EdgeInsets.symmetric(vertical: 14, horizontal: 6),
+ EdgeInsets.symmetric(horizontal: 6, vertical: 2),
),
),
label: FlowyText.medium(
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/row_page_button.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/row_page_button.dart
new file mode 100644
index 0000000000..49f95887ab
--- /dev/null
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/row_page_button.dart
@@ -0,0 +1,143 @@
+import 'dart:async';
+
+import 'package:appflowy/generated/flowy_svgs.g.dart';
+import 'package:appflowy/generated/locale_keys.g.dart';
+import 'package:appflowy/mobile/application/mobile_router.dart';
+import 'package:appflowy/plugins/database/application/cell/bloc/text_cell_bloc.dart';
+import 'package:appflowy/plugins/database/application/cell/cell_controller.dart';
+import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
+import 'package:appflowy/plugins/database/application/database_controller.dart';
+import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
+import 'package:appflowy/workspace/application/view/prelude.dart';
+import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
+import 'package:appflowy_backend/log.dart';
+import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+class OpenRowPageButton extends StatefulWidget {
+ const OpenRowPageButton({
+ super.key,
+ required this.documentId,
+ required this.databaseController,
+ required this.cellContext,
+ });
+
+ final String documentId;
+
+ final DatabaseController databaseController;
+ final CellContext cellContext;
+
+ @override
+ State createState() => _OpenRowPageButtonState();
+}
+
+class _OpenRowPageButtonState extends State {
+ late final cellBloc = TextCellBloc(
+ cellController: makeCellController(
+ widget.databaseController,
+ widget.cellContext,
+ ).as(),
+ );
+
+ ViewPB? view;
+
+ @override
+ void initState() {
+ super.initState();
+
+ _preloadView(context, createDocumentIfMissed: true);
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return BlocBuilder(
+ bloc: cellBloc,
+ builder: (context, state) {
+ return ConstrainedBox(
+ constraints: BoxConstraints(
+ minWidth: double.infinity,
+ maxHeight: GridSize.buttonHeight,
+ ),
+ child: TextButton.icon(
+ style: Theme.of(context).textButtonTheme.style?.copyWith(
+ shape: WidgetStateProperty.all(
+ RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(12.0),
+ ),
+ ),
+ overlayColor: WidgetStateProperty.all(
+ Theme.of(context).hoverColor,
+ ),
+ alignment: AlignmentDirectional.centerStart,
+ splashFactory: NoSplash.splashFactory,
+ padding: const WidgetStatePropertyAll(
+ EdgeInsets.symmetric(horizontal: 6),
+ ),
+ ),
+ label: FlowyText.medium(
+ LocaleKeys.grid_field_openRowDocument.tr(),
+ fontSize: 15,
+ ),
+ icon: const Padding(
+ padding: EdgeInsets.all(4.0),
+ child: FlowySvg(
+ FlowySvgs.full_view_s,
+ size: Size.square(16.0),
+ ),
+ ),
+ onPressed: () {
+ final name = state.content;
+ _openRowPage(context, name);
+ },
+ ),
+ );
+ },
+ );
+ }
+
+ Future _openRowPage(BuildContext context, String fieldName) async {
+ Log.info('Open row page(${widget.documentId})');
+
+ if (view == null) {
+ showToastNotification(context, message: 'Failed to open row page');
+ // reload the view again
+ unawaited(_preloadView(context));
+ Log.error('Failed to open row page(${widget.documentId})');
+ return;
+ }
+
+ if (context.mounted) {
+ // the document in row is an orphan document, so we don't add it to recent
+ await context.pushView(
+ view!,
+ addInRecent: false,
+ showMoreButton: false,
+ fixedTitle: fieldName,
+ );
+ }
+ }
+
+ // preload view to reduce the time to open the view
+ Future _preloadView(
+ BuildContext context, {
+ bool createDocumentIfMissed = false,
+ }) async {
+ Log.info('Preload row page(${widget.documentId})');
+ final result = await ViewBackendService.getView(widget.documentId);
+ view = result.fold((s) => s, (f) => null);
+
+ if (view == null && createDocumentIfMissed) {
+ // create view if not exists
+ Log.info('Create row page(${widget.documentId})');
+ final result = await ViewBackendService.createOrphanView(
+ name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
+ viewId: widget.documentId,
+ layoutType: ViewLayoutPB.Document,
+ );
+ view = result.fold((s) => s, (f) => null);
+ }
+ }
+}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/editor/mobile_editor_screen.dart b/frontend/appflowy_flutter/lib/mobile/presentation/editor/mobile_editor_screen.dart
index 14c4e022ae..aacc055e74 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/editor/mobile_editor_screen.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/editor/mobile_editor_screen.dart
@@ -7,15 +7,21 @@ class MobileDocumentScreen extends StatelessWidget {
super.key,
required this.id,
this.title,
+ this.showMoreButton = true,
+ this.fixedTitle,
});
/// view id
final String id;
final String? title;
+ final bool showMoreButton;
+ final String? fixedTitle;
static const routeName = '/docs';
static const viewId = 'id';
static const viewTitle = 'title';
+ static const viewShowMoreButton = 'show_more_button';
+ static const viewFixedTitle = 'fixed_title';
@override
Widget build(BuildContext context) {
@@ -23,6 +29,8 @@ class MobileDocumentScreen extends StatelessWidget {
id: id,
title: title,
viewLayout: ViewLayoutPB.Document,
+ showMoreButton: showMoreButton,
+ fixedTitle: fixedTitle,
);
}
}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/favorite_folder/favorite_space.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/favorite_folder/favorite_space.dart
index 36e6c57e85..6282421109 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/home/favorite_folder/favorite_space.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/favorite_folder/favorite_space.dart
@@ -96,36 +96,34 @@ class _FavoriteViews extends StatelessWidget {
final borderColor = Theme.of(context).isLightMode
? const Color(0xFFE9E9EC)
: const Color(0x1AFFFFFF);
- return Scrollbar(
- child: ListView.separated(
- key: const PageStorageKey('favorite_views_page_storage_key'),
- padding: EdgeInsets.only(
- bottom: HomeSpaceViewSizes.mVerticalPadding +
- MediaQuery.of(context).padding.bottom,
- ),
- itemBuilder: (context, index) {
- final view = favoriteViews[index];
- return Container(
- padding: const EdgeInsets.symmetric(vertical: 24.0),
- decoration: BoxDecoration(
- border: Border(
- bottom: BorderSide(
- color: borderColor,
- width: 0.5,
- ),
+ return ListView.separated(
+ key: const PageStorageKey('favorite_views_page_storage_key'),
+ padding: EdgeInsets.only(
+ bottom: HomeSpaceViewSizes.mVerticalPadding +
+ MediaQuery.of(context).padding.bottom,
+ ),
+ itemBuilder: (context, index) {
+ final view = favoriteViews[index];
+ return Container(
+ padding: const EdgeInsets.symmetric(vertical: 24.0),
+ decoration: BoxDecoration(
+ border: Border(
+ bottom: BorderSide(
+ color: borderColor,
+ width: 0.5,
),
),
- child: MobileViewPage(
- key: ValueKey(view.item.id),
- view: view.item,
- timestamp: view.timestamp,
- type: MobilePageCardType.favorite,
- ),
- );
- },
- separatorBuilder: (context, index) => const HSpace(8),
- itemCount: favoriteViews.length,
- ),
+ ),
+ child: MobileViewPage(
+ key: ValueKey(view.item.id),
+ view: view.item,
+ timestamp: view.timestamp,
+ type: MobilePageCardType.favorite,
+ ),
+ );
+ },
+ separatorBuilder: (context, index) => const HSpace(8),
+ itemCount: favoriteViews.length,
);
}
}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/home_space/home_space.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/home_space/home_space.dart
index 02e5fce9ab..5651379522 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/home/home_space/home_space.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/home_space/home_space.dart
@@ -25,19 +25,17 @@ class _MobileHomeSpaceState extends State
final workspaceId =
context.read().state.currentWorkspace?.workspaceId ??
'';
- return Scrollbar(
- child: SingleChildScrollView(
- child: Padding(
- padding: EdgeInsets.only(
- top: HomeSpaceViewSizes.mVerticalPadding,
- bottom: HomeSpaceViewSizes.mVerticalPadding +
- MediaQuery.of(context).padding.bottom,
- ),
- child: MobileFolders(
- user: widget.userProfile,
- workspaceId: workspaceId,
- showFavorite: false,
- ),
+ return SingleChildScrollView(
+ child: Padding(
+ padding: EdgeInsets.only(
+ top: HomeSpaceViewSizes.mVerticalPadding,
+ bottom: HomeSpaceViewSizes.mVerticalPadding +
+ MediaQuery.of(context).padding.bottom,
+ ),
+ child: MobileFolders(
+ user: widget.userProfile,
+ workspaceId: workspaceId,
+ showFavorite: false,
),
),
);
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_folders.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_folders.dart
index a5f4c210d7..dc9602ca13 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_folders.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_folders.dart
@@ -35,6 +35,9 @@ class MobileFolders extends StatelessWidget {
context.read().state.currentWorkspace?.workspaceId ??
'';
return BlocListener(
+ listenWhen: (previous, current) =>
+ previous.currentWorkspace?.workspaceId !=
+ current.currentWorkspace?.workspaceId,
listener: (context, state) {
context.read().add(
SidebarSectionsEvent.initial(
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page.dart
index 215c9433b5..d3a0a5ed23 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page.dart
@@ -1,8 +1,10 @@
import 'dart:io';
+import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/home/mobile_home_page_header.dart';
import 'package:appflowy/mobile/presentation/home/tab/mobile_space_tab.dart';
import 'package:appflowy/mobile/presentation/home/tab/space_order_bloc.dart';
+import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/loading.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
@@ -14,14 +16,19 @@ import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
import 'package:appflowy/workspace/presentation/home/errors/workspace_failed_screen.dart';
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart';
+import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
+import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
-import 'package:appflowy_editor/appflowy_editor.dart';
+import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
+import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:provider/provider.dart';
+import 'package:sentry/sentry.dart';
+import 'package:toastification/toastification.dart';
class MobileHomeScreen extends StatelessWidget {
const MobileHomeScreen({super.key});
@@ -59,6 +66,14 @@ class MobileHomeScreen extends StatelessWidget {
return const WorkspaceFailedScreen();
}
+ Sentry.configureScope(
+ (scope) => scope.setUser(
+ SentryUser(
+ id: userProfile.id.toString(),
+ ),
+ ),
+ );
+
return Scaffold(
body: SafeArea(
bottom: false,
@@ -94,6 +109,8 @@ class MobileHomePage extends StatefulWidget {
}
class _MobileHomePageState extends State {
+ Loading? loadingIndicator;
+
@override
void initState() {
super.initState();
@@ -125,75 +142,7 @@ class _MobileHomePageState extends State {
value: getIt()..add(const ReminderEvent.started()),
),
],
- child: BlocConsumer(
- buildWhen: (previous, current) =>
- previous.currentWorkspace?.workspaceId !=
- current.currentWorkspace?.workspaceId,
- listener: (context, state) {
- getIt().reset();
- mCurrentWorkspace.value = state.currentWorkspace;
- },
- builder: (context, state) {
- if (state.currentWorkspace == null) {
- return const SizedBox.shrink();
- }
-
- final workspaceId = state.currentWorkspace!.workspaceId;
-
- return Column(
- children: [
- // Header
- Padding(
- padding: EdgeInsets.only(
- left: HomeSpaceViewSizes.mHorizontalPadding,
- right: 8.0,
- top: Platform.isAndroid ? 8.0 : 0.0,
- ),
- child: MobileHomePageHeader(
- userProfile: widget.userProfile,
- ),
- ),
-
- Expanded(
- child: MultiBlocProvider(
- providers: [
- BlocProvider(
- create: (_) => SpaceOrderBloc()
- ..add(const SpaceOrderEvent.initial()),
- ),
- BlocProvider(
- create: (_) => SidebarSectionsBloc()
- ..add(
- SidebarSectionsEvent.initial(
- widget.userProfile,
- workspaceId,
- ),
- ),
- ),
- BlocProvider(
- create: (_) =>
- FavoriteBloc()..add(const FavoriteEvent.initial()),
- ),
- BlocProvider(
- create: (_) => SpaceBloc()
- ..add(
- SpaceEvent.initial(
- widget.userProfile,
- workspaceId,
- openFirstPage: false,
- ),
- ),
- ),
- ],
- child: MobileSpaceTab(
- userProfile: widget.userProfile,
- ),
- ),
- ),
- ],
- );
- },
- ),
+ child: _HomePage(userProfile: widget.userProfile),
);
}
@@ -205,3 +154,147 @@ class _MobileHomePageState extends State {
await FolderEventSetLatestView(ViewIdPB(value: id)).send();
}
}
+
+class _HomePage extends StatefulWidget {
+ const _HomePage({required this.userProfile});
+
+ final UserProfilePB userProfile;
+
+ @override
+ State<_HomePage> createState() => _HomePageState();
+}
+
+class _HomePageState extends State<_HomePage> {
+ Loading? loadingIndicator;
+
+ @override
+ Widget build(BuildContext context) {
+ return BlocConsumer(
+ buildWhen: (previous, current) =>
+ previous.currentWorkspace?.workspaceId !=
+ current.currentWorkspace?.workspaceId,
+ listener: (context, state) {
+ getIt().reset();
+ mCurrentWorkspace.value = state.currentWorkspace;
+
+ _showResultDialog(context, state);
+ },
+ builder: (context, state) {
+ if (state.currentWorkspace == null) {
+ return const SizedBox.shrink();
+ }
+
+ final workspaceId = state.currentWorkspace!.workspaceId;
+
+ return Column(
+ key: ValueKey('mobile_home_page_$workspaceId'),
+ children: [
+ // Header
+ Padding(
+ padding: EdgeInsets.only(
+ left: HomeSpaceViewSizes.mHorizontalPadding,
+ right: 8.0,
+ top: Platform.isAndroid ? 8.0 : 0.0,
+ ),
+ child: MobileHomePageHeader(
+ userProfile: widget.userProfile,
+ ),
+ ),
+
+ Expanded(
+ child: MultiBlocProvider(
+ providers: [
+ BlocProvider(
+ create: (_) =>
+ SpaceOrderBloc()..add(const SpaceOrderEvent.initial()),
+ ),
+ BlocProvider(
+ create: (_) => SidebarSectionsBloc()
+ ..add(
+ SidebarSectionsEvent.initial(
+ widget.userProfile,
+ workspaceId,
+ ),
+ ),
+ ),
+ BlocProvider(
+ create: (_) =>
+ FavoriteBloc()..add(const FavoriteEvent.initial()),
+ ),
+ BlocProvider(
+ create: (_) => SpaceBloc()
+ ..add(
+ SpaceEvent.initial(
+ widget.userProfile,
+ workspaceId,
+ openFirstPage: false,
+ ),
+ ),
+ ),
+ ],
+ child: MobileSpaceTab(
+ userProfile: widget.userProfile,
+ ),
+ ),
+ ),
+ ],
+ );
+ },
+ );
+ }
+
+ void _showResultDialog(BuildContext context, UserWorkspaceState state) {
+ final actionResult = state.actionResult;
+ if (actionResult == null) {
+ return;
+ }
+
+ final actionType = actionResult.actionType;
+ final result = actionResult.result;
+ final isLoading = actionResult.isLoading;
+
+ if (isLoading) {
+ loadingIndicator ??= Loading(context)..start();
+ return;
+ } else {
+ loadingIndicator?.stop();
+ loadingIndicator = null;
+ }
+
+ if (result == null) {
+ return;
+ }
+
+ result.onFailure((f) {
+ Log.error(
+ '[Workspace] Failed to perform ${actionType.toString()} action: $f',
+ );
+ });
+
+ final String? message;
+ ToastificationType toastType = ToastificationType.success;
+ switch (actionType) {
+ case UserWorkspaceActionType.open:
+ message = result.fold(
+ (s) {
+ toastType = ToastificationType.success;
+ return LocaleKeys.workspace_openSuccess.tr();
+ },
+ (e) {
+ toastType = ToastificationType.error;
+ return '${LocaleKeys.workspace_openFailed.tr()}: ${e.msg}';
+ },
+ );
+ break;
+
+ default:
+ message = null;
+ toastType = ToastificationType.error;
+ break;
+ }
+
+ if (message != null) {
+ showToastNotification(context, message: message, type: toastType);
+ }
+ }
+}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_setting_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_setting_page.dart
index 964f9e5aa5..07ee4de7d6 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_setting_page.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_setting_page.dart
@@ -5,6 +5,7 @@ import 'package:appflowy/mobile/presentation/base/app_bar/app_bar.dart';
import 'package:appflowy/mobile/presentation/presentation.dart';
import 'package:appflowy/mobile/presentation/setting/cloud/cloud_setting_group.dart';
import 'package:appflowy/mobile/presentation/setting/user_session_setting_group.dart';
+import 'package:appflowy/mobile/presentation/setting/workspace/workspace_setting_group.dart';
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
@@ -79,8 +80,7 @@ class _MobileHomeSettingPageState extends State {
PersonalInfoSettingGroup(
userProfile: userProfile,
),
- // TODO: Enable and implement along with Push Notifications
- // const NotificationsSettingGroup(),
+ const WorkspaceSettingGroup(),
const AppearanceSettingGroup(),
const LanguageSettingGroup(),
if (Env.enableCustomCloud) const CloudSettingGroup(),
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/recent_folder/recent_space.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/recent_folder/recent_space.dart
index e06506936c..c0baa641d9 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/home/recent_folder/recent_space.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/recent_folder/recent_space.dart
@@ -68,36 +68,34 @@ class _RecentViews extends StatelessWidget {
? const Color(0xFFE9E9EC)
: const Color(0x1AFFFFFF);
return SlidableAutoCloseBehavior(
- child: Scrollbar(
- child: ListView.separated(
- key: const PageStorageKey('recent_views_page_storage_key'),
- padding: EdgeInsets.only(
- bottom: HomeSpaceViewSizes.mVerticalPadding +
- MediaQuery.of(context).padding.bottom,
- ),
- itemBuilder: (context, index) {
- final sectionView = recentViews[index];
- return Container(
- padding: const EdgeInsets.symmetric(vertical: 24.0),
- decoration: BoxDecoration(
- border: Border(
- bottom: BorderSide(
- color: borderColor,
- width: 0.5,
- ),
+ child: ListView.separated(
+ key: const PageStorageKey('recent_views_page_storage_key'),
+ padding: EdgeInsets.only(
+ bottom: HomeSpaceViewSizes.mVerticalPadding +
+ MediaQuery.of(context).padding.bottom,
+ ),
+ itemBuilder: (context, index) {
+ final sectionView = recentViews[index];
+ return Container(
+ padding: const EdgeInsets.symmetric(vertical: 24.0),
+ decoration: BoxDecoration(
+ border: Border(
+ bottom: BorderSide(
+ color: borderColor,
+ width: 0.5,
),
),
- child: MobileViewPage(
- key: ValueKey(sectionView.item.id),
- view: sectionView.item,
- timestamp: sectionView.timestamp,
- type: MobilePageCardType.recent,
- ),
- );
- },
- separatorBuilder: (context, index) => const HSpace(8),
- itemCount: recentViews.length,
- ),
+ ),
+ child: MobileViewPage(
+ key: ValueKey(sectionView.item.id),
+ view: sectionView.item,
+ timestamp: sectionView.timestamp,
+ type: MobilePageCardType.recent,
+ ),
+ );
+ },
+ separatorBuilder: (context, index) => const HSpace(8),
+ itemCount: recentViews.length,
),
);
}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/ai_bubble_button.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/ai_bubble_button.dart
new file mode 100644
index 0000000000..8ecd70f7e5
--- /dev/null
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/ai_bubble_button.dart
@@ -0,0 +1,81 @@
+import 'package:appflowy/generated/flowy_svgs.g.dart';
+import 'package:appflowy/generated/locale_keys.g.dart';
+import 'package:appflowy/mobile/presentation/base/gesture.dart';
+import 'package:appflowy/mobile/presentation/home/tab/mobile_space_tab.dart';
+import 'package:appflowy/util/theme_extension.dart';
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flutter/material.dart';
+
+class FloatingAIEntry extends StatelessWidget {
+ const FloatingAIEntry({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return AnimatedGestureDetector(
+ scaleFactor: 0.99,
+ onTapUp: () => mobileCreateNewAIChatNotifier.value =
+ mobileCreateNewAIChatNotifier.value + 1,
+ child: DecoratedBox(
+ decoration: _buildShadowDecoration(context),
+ child: Container(
+ decoration: _buildWrapperDecoration(context),
+ height: 48,
+ alignment: Alignment.centerLeft,
+ child: Padding(
+ padding: const EdgeInsets.only(left: 18),
+ child: _buildHintText(context),
+ ),
+ ),
+ ),
+ );
+ }
+
+ BoxDecoration _buildShadowDecoration(BuildContext context) {
+ return BoxDecoration(
+ borderRadius: BorderRadius.circular(30),
+ boxShadow: [
+ BoxShadow(
+ blurRadius: 20,
+ spreadRadius: 1,
+ offset: const Offset(0, 4),
+ color: Colors.black.withOpacity(0.05),
+ ),
+ ],
+ );
+ }
+
+ BoxDecoration _buildWrapperDecoration(BuildContext context) {
+ final outlineColor = Theme.of(context).colorScheme.outline;
+ final borderColor = Theme.of(context).isLightMode
+ ? outlineColor.withOpacity(0.7)
+ : outlineColor.withOpacity(0.3);
+ return BoxDecoration(
+ borderRadius: BorderRadius.circular(30),
+ color: Theme.of(context).colorScheme.surface,
+ border: Border.fromBorderSide(
+ BorderSide(
+ color: borderColor,
+ ),
+ ),
+ );
+ }
+
+ Widget _buildHintText(BuildContext context) {
+ return Row(
+ children: [
+ FlowySvg(
+ FlowySvgs.toolbar_item_ai_s,
+ size: const Size.square(16.0),
+ color: Theme.of(context).hintColor,
+ opacity: 0.7,
+ ),
+ const HSpace(8),
+ FlowyText(
+ LocaleKeys.chat_inputMessageHint.tr(),
+ color: Theme.of(context).hintColor,
+ ),
+ ],
+ );
+ }
+}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/mobile_space_tab.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/mobile_space_tab.dart
index fe36c392b4..1c0f5933fb 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/mobile_space_tab.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/mobile_space_tab.dart
@@ -6,9 +6,12 @@ import 'package:appflowy/mobile/presentation/home/recent_folder/recent_space.dar
import 'package:appflowy/mobile/presentation/home/tab/_tab_bar.dart';
import 'package:appflowy/mobile/presentation/home/tab/space_order_bloc.dart';
import 'package:appflowy/mobile/presentation/presentation.dart';
+import 'package:appflowy/mobile/presentation/setting/workspace/invite_members_screen.dart';
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
+import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
+import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
@@ -17,6 +20,10 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:provider/provider.dart';
+import 'ai_bubble_button.dart';
+
+final ValueNotifier mobileCreateNewAIChatNotifier = ValueNotifier(0);
+
class MobileSpaceTab extends StatefulWidget {
const MobileSpaceTab({
super.key,
@@ -37,14 +44,19 @@ class _MobileSpaceTabState extends State
void initState() {
super.initState();
- mobileCreateNewPageNotifier.addListener(_createNewPage);
+ mobileCreateNewPageNotifier.addListener(_createNewDocument);
+ mobileCreateNewAIChatNotifier.addListener(_createNewAIChat);
+ mobileLeaveWorkspaceNotifier.addListener(_leaveWorkspace);
}
@override
void dispose() {
tabController?.removeListener(_onTabChange);
tabController?.dispose();
- mobileCreateNewPageNotifier.removeListener(_createNewPage);
+
+ mobileCreateNewPageNotifier.removeListener(_createNewDocument);
+ mobileCreateNewAIChatNotifier.removeListener(_createNewAIChat);
+ mobileLeaveWorkspaceNotifier.removeListener(_leaveWorkspace);
super.dispose();
}
@@ -140,7 +152,20 @@ class _MobileSpaceTabState extends State
case MobileSpaceTabType.recent:
return const MobileRecentSpace();
case MobileSpaceTabType.spaces:
- return MobileHomeSpace(userProfile: widget.userProfile);
+ return Stack(
+ children: [
+ MobileHomeSpace(userProfile: widget.userProfile),
+ // only show ai chat button for cloud user
+ if (widget.userProfile.authenticator ==
+ AuthenticatorPB.AppFlowyCloud)
+ Positioned(
+ bottom: MediaQuery.of(context).padding.bottom + 16,
+ left: 20,
+ right: 20,
+ child: const FloatingAIEntry(),
+ ),
+ ],
+ );
case MobileSpaceTabType.favorites:
return MobileFavoriteSpace(userProfile: widget.userProfile);
default:
@@ -150,15 +175,24 @@ class _MobileSpaceTabState extends State
}
// quick create new page when clicking the add button in navigation bar
- void _createNewPage() {
+ void _createNewDocument() {
+ _createNewPage(ViewLayoutPB.Document);
+ }
+
+ void _createNewAIChat() {
+ _createNewPage(ViewLayoutPB.Chat);
+ }
+
+ void _createNewPage(ViewLayoutPB layout) {
if (context.read().state.spaces.isNotEmpty) {
context.read().add(
SpaceEvent.createPage(
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
- layout: ViewLayoutPB.Document,
+ layout: layout,
),
);
- } else {
+ } else if (layout == ViewLayoutPB.Document) {
+ // only support create document in section
context.read().add(
SidebarSectionsEvent.createRootViewInSection(
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
@@ -171,4 +205,16 @@ class _MobileSpaceTabState extends State
);
}
}
+
+ void _leaveWorkspace() {
+ final workspaceId =
+ context.read().state.currentWorkspace?.workspaceId;
+ if (workspaceId == null) {
+ Log.error('Workspace ID is null');
+ return;
+ }
+ context
+ .read()
+ .add(UserWorkspaceEvent.leaveWorkspace(workspaceId));
+ }
}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/support_setting_group.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/support_setting_group.dart
index 5222a05b8f..584b867736 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/support_setting_group.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/support_setting_group.dart
@@ -7,7 +7,8 @@ import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
import 'package:appflowy/shared/appflowy_cache_manager.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/util/share_log_files.dart';
-import 'package:appflowy/workspace/presentation/home/toast.dart';
+import 'package:appflowy/workspace/presentation/settings/pages/fix_data_widget.dart';
+import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
@@ -74,10 +75,14 @@ class SupportSettingGroup extends StatelessWidget {
actionButtonTitle: LocaleKeys.button_yes.tr(),
onActionButtonPressed: () async {
await getIt().clearAllCache();
+ // check the workspace and space health
+ await WorkspaceDataManager.checkViewHealth(
+ dryRun: false,
+ );
if (context.mounted) {
- showSnackBarMessage(
+ showToastNotification(
context,
- LocaleKeys.settings_files_clearCacheSuccess.tr(),
+ message: LocaleKeys.settings_files_clearCacheSuccess.tr(),
);
}
},
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/user_session_setting_group.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/user_session_setting_group.dart
index 8f8fd99ecb..1145d08048 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/user_session_setting_group.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/user_session_setting_group.dart
@@ -42,14 +42,24 @@ class UserSessionSettingGroup extends StatelessWidget {
MobileSignInOrLogoutButton(
labelText: LocaleKeys.settings_menu_logout.tr(),
onPressed: () async {
- await showFlowyMobileConfirmDialog(
- context,
- content: FlowyText(
- LocaleKeys.settings_menu_logoutPrompt.tr(),
+ await showFlowyCupertinoConfirmDialog(
+ title: LocaleKeys.settings_menu_logoutPrompt.tr(),
+ leftButton: FlowyText(
+ LocaleKeys.button_cancel.tr(),
+ fontSize: 17.0,
+ figmaLineHeight: 24.0,
+ fontWeight: FontWeight.w500,
+ color: const Color(0xFF007AFF),
),
- actionButtonTitle: LocaleKeys.button_yes.tr(),
- actionButtonColor: Theme.of(context).colorScheme.error,
- onActionButtonPressed: () async {
+ rightButton: FlowyText(
+ LocaleKeys.button_logout.tr(),
+ fontSize: 17.0,
+ figmaLineHeight: 24.0,
+ fontWeight: FontWeight.w400,
+ color: const Color(0xFFFE0220),
+ ),
+ onRightButtonPressed: (context) async {
+ Navigator.of(context).pop();
await getIt().signOut();
await runAppFlowy();
},
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/workspace/invite_members_screen.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/workspace/invite_members_screen.dart
new file mode 100644
index 0000000000..1aa088d963
--- /dev/null
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/workspace/invite_members_screen.dart
@@ -0,0 +1,346 @@
+import 'package:appflowy/generated/locale_keys.g.dart';
+import 'package:appflowy/mobile/presentation/base/app_bar/app_bar.dart';
+import 'package:appflowy/mobile/presentation/widgets/show_flowy_mobile_confirm_dialog.dart';
+import 'package:appflowy/shared/af_role_pb_extension.dart';
+import 'package:appflowy/user/application/user_service.dart';
+import 'package:appflowy/workspace/presentation/settings/widgets/members/workspace_member_bloc.dart';
+import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
+import 'package:appflowy_backend/log.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/code.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
+import 'package:appflowy_result/appflowy_result.dart';
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:string_validator/string_validator.dart';
+import 'package:toastification/toastification.dart';
+
+import 'member_list.dart';
+
+ValueNotifier mobileLeaveWorkspaceNotifier = ValueNotifier(0);
+
+class InviteMembersScreen extends StatelessWidget {
+ const InviteMembersScreen({
+ super.key,
+ });
+
+ static const routeName = '/invite_member';
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: FlowyAppBar(
+ titleText: LocaleKeys.settings_appearance_members_label.tr(),
+ ),
+ body: const _InviteMemberPage(),
+ );
+ }
+}
+
+class _InviteMemberPage extends StatefulWidget {
+ const _InviteMemberPage();
+
+ @override
+ State<_InviteMemberPage> createState() => _InviteMemberPageState();
+}
+
+class _InviteMemberPageState extends State<_InviteMemberPage> {
+ final emailController = TextEditingController();
+ late final Future userProfile;
+ bool exceededLimit = false;
+
+ @override
+ void initState() {
+ super.initState();
+ userProfile = UserBackendService.getCurrentUserProfile().fold(
+ (s) => s,
+ (f) => null,
+ );
+ }
+
+ @override
+ void dispose() {
+ emailController.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return FutureBuilder(
+ future: userProfile,
+ builder: (context, snapshot) {
+ if (snapshot.connectionState == ConnectionState.waiting) {
+ return const SizedBox.shrink();
+ }
+ if (snapshot.hasError || snapshot.data == null) {
+ return _buildError(context);
+ }
+
+ final userProfile = snapshot.data!;
+
+ return BlocProvider(
+ create: (context) => WorkspaceMemberBloc(userProfile: userProfile)
+ ..add(const WorkspaceMemberEvent.initial()),
+ child: BlocConsumer(
+ listener: _onListener,
+ builder: (context, state) {
+ return Column(
+ children: [
+ Expanded(
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ if (state.myRole.isOwner) ...[
+ Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: _buildInviteMemberArea(context),
+ ),
+ const VSpace(16),
+ ],
+ if (state.members.isNotEmpty) ...[
+ const VSpace(8),
+ MobileMemberList(
+ members: state.members,
+ userProfile: userProfile,
+ myRole: state.myRole,
+ ),
+ ],
+ ],
+ ),
+ ),
+ if (state.myRole.isMember) const _LeaveWorkspaceButton(),
+ const VSpace(48),
+ ],
+ );
+ },
+ ),
+ );
+ },
+ );
+ }
+
+ Widget _buildInviteMemberArea(BuildContext context) {
+ return Column(
+ children: [
+ TextFormField(
+ autofocus: true,
+ controller: emailController,
+ keyboardType: TextInputType.text,
+ decoration: InputDecoration(
+ hintText: LocaleKeys.settings_appearance_members_inviteHint.tr(),
+ ),
+ ),
+ const VSpace(16),
+ if (exceededLimit) ...[
+ FlowyText.regular(
+ LocaleKeys.settings_appearance_members_inviteFailedMemberLimit.tr(),
+ fontSize: 14.0,
+ maxLines: 3,
+ color: Theme.of(context).colorScheme.error,
+ ),
+ const VSpace(16),
+ ],
+ SizedBox(
+ width: double.infinity,
+ child: ElevatedButton(
+ onPressed: () => _inviteMember(context),
+ child: Text(
+ LocaleKeys.settings_appearance_members_sendInvite.tr(),
+ ),
+ ),
+ ),
+ ],
+ );
+ }
+
+ Widget _buildError(BuildContext context) {
+ return Center(
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 48.0),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ FlowyText.medium(
+ LocaleKeys.settings_appearance_members_workspaceMembersError.tr(),
+ fontSize: 18.0,
+ textAlign: TextAlign.center,
+ ),
+ const VSpace(8.0),
+ FlowyText.regular(
+ LocaleKeys
+ .settings_appearance_members_workspaceMembersErrorDescription
+ .tr(),
+ fontSize: 17.0,
+ maxLines: 10,
+ textAlign: TextAlign.center,
+ lineHeight: 1.3,
+ color: Theme.of(context).hintColor,
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ void _onListener(BuildContext context, WorkspaceMemberState state) {
+ final actionResult = state.actionResult;
+ if (actionResult == null) {
+ return;
+ }
+
+ final actionType = actionResult.actionType;
+ final result = actionResult.result;
+
+ // only show the result dialog when the action is WorkspaceMemberActionType.add
+ if (actionType == WorkspaceMemberActionType.add) {
+ result.fold(
+ (s) {
+ showToastNotification(
+ context,
+ message:
+ LocaleKeys.settings_appearance_members_addMemberSuccess.tr(),
+ );
+ },
+ (f) {
+ Log.error('add workspace member failed: $f');
+ final message = f.code == ErrorCode.WorkspaceMemberLimitExceeded
+ ? LocaleKeys.settings_appearance_members_memberLimitExceeded.tr()
+ : LocaleKeys.settings_appearance_members_failedToAddMember.tr();
+ setState(() {
+ exceededLimit = f.code == ErrorCode.WorkspaceMemberLimitExceeded;
+ });
+ showToastNotification(
+ context,
+ type: ToastificationType.error,
+ message: message,
+ );
+ },
+ );
+ } else if (actionType == WorkspaceMemberActionType.invite) {
+ result.fold(
+ (s) {
+ showToastNotification(
+ context,
+ message:
+ LocaleKeys.settings_appearance_members_inviteMemberSuccess.tr(),
+ );
+ },
+ (f) {
+ Log.error('invite workspace member failed: $f');
+ final message = f.code == ErrorCode.WorkspaceMemberLimitExceeded
+ ? LocaleKeys.settings_appearance_members_inviteFailedMemberLimit
+ .tr()
+ : LocaleKeys.settings_appearance_members_failedToInviteMember
+ .tr();
+ setState(() {
+ exceededLimit = f.code == ErrorCode.WorkspaceMemberLimitExceeded;
+ });
+ showToastNotification(
+ context,
+ type: ToastificationType.error,
+ message: message,
+ );
+ },
+ );
+ } else if (actionType == WorkspaceMemberActionType.remove) {
+ result.fold(
+ (s) {
+ showToastNotification(
+ context,
+ message: LocaleKeys
+ .settings_appearance_members_removeFromWorkspaceSuccess
+ .tr(),
+ );
+ },
+ (f) {
+ showToastNotification(
+ context,
+ type: ToastificationType.error,
+ message: LocaleKeys
+ .settings_appearance_members_removeFromWorkspaceFailed
+ .tr(),
+ );
+ },
+ );
+ }
+ }
+
+ void _inviteMember(BuildContext context) {
+ final email = emailController.text;
+ if (!isEmail(email)) {
+ return showToastNotification(
+ context,
+ type: ToastificationType.error,
+ message: LocaleKeys.settings_appearance_members_emailInvalidError.tr(),
+ );
+ }
+ context
+ .read()
+ .add(WorkspaceMemberEvent.inviteWorkspaceMember(email));
+ // clear the email field after inviting
+ emailController.clear();
+ }
+}
+
+class _LeaveWorkspaceButton extends StatelessWidget {
+ const _LeaveWorkspaceButton();
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ width: double.infinity,
+ margin: const EdgeInsets.symmetric(horizontal: 16),
+ child: ElevatedButton(
+ style: ElevatedButton.styleFrom(
+ backgroundColor: Colors.transparent,
+ foregroundColor: Theme.of(context).colorScheme.error,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(4),
+ side: BorderSide(
+ color: Theme.of(context).colorScheme.error,
+ width: 0.5,
+ ),
+ ),
+ ),
+ onPressed: () => _leaveWorkspace(context),
+ child: FlowyText(
+ LocaleKeys.workspace_leaveCurrentWorkspace.tr(),
+ fontSize: 14.0,
+ color: Theme.of(context).colorScheme.error,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ );
+ }
+
+ void _leaveWorkspace(BuildContext context) {
+ showFlowyCupertinoConfirmDialog(
+ title: LocaleKeys.workspace_leaveCurrentWorkspacePrompt.tr(),
+ leftButton: FlowyText(
+ LocaleKeys.button_cancel.tr(),
+ fontSize: 17.0,
+ figmaLineHeight: 24.0,
+ fontWeight: FontWeight.w500,
+ color: const Color(0xFF007AFF),
+ ),
+ rightButton: FlowyText(
+ LocaleKeys.button_confirm.tr(),
+ fontSize: 17.0,
+ figmaLineHeight: 24.0,
+ fontWeight: FontWeight.w400,
+ color: const Color(0xFFFE0220),
+ ),
+ onRightButtonPressed: (buttonContext) async {
+ // try to use popUntil with a specific route name but failed
+ // so use pop twice as a workaround
+ Navigator.of(buttonContext).pop();
+ Navigator.of(context).pop();
+ Navigator.of(context).pop();
+
+ mobileLeaveWorkspaceNotifier.value =
+ mobileLeaveWorkspaceNotifier.value + 1;
+ },
+ );
+ }
+}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/workspace/member_list.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/workspace/member_list.dart
new file mode 100644
index 0000000000..1d9f250d3a
--- /dev/null
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/workspace/member_list.dart
@@ -0,0 +1,164 @@
+import 'package:appflowy/generated/flowy_svgs.g.dart';
+import 'package:appflowy/generated/locale_keys.g.dart';
+import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
+import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
+import 'package:appflowy/shared/af_role_pb_extension.dart';
+import 'package:appflowy/workspace/presentation/settings/widgets/members/workspace_member_bloc.dart';
+import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:flutter_slidable/flutter_slidable.dart';
+
+class MobileMemberList extends StatelessWidget {
+ const MobileMemberList({
+ super.key,
+ required this.members,
+ required this.myRole,
+ required this.userProfile,
+ });
+
+ final List members;
+ final AFRolePB myRole;
+ final UserProfilePB userProfile;
+
+ @override
+ Widget build(BuildContext context) {
+ return SlidableAutoCloseBehavior(
+ child: SeparatedColumn(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ separatorBuilder: () => const FlowyDivider(
+ padding: EdgeInsets.symmetric(horizontal: 16.0),
+ ),
+ children: [
+ Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 16.0,
+ vertical: 8.0,
+ ),
+ child: FlowyText.semibold(
+ LocaleKeys.settings_appearance_members_label.tr(),
+ fontSize: 16.0,
+ ),
+ ),
+ ...members.map(
+ (member) => _MemberItem(
+ member: member,
+ myRole: myRole,
+ userProfile: userProfile,
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
+
+class _MemberItem extends StatelessWidget {
+ const _MemberItem({
+ required this.member,
+ required this.myRole,
+ required this.userProfile,
+ });
+
+ final WorkspaceMemberPB member;
+ final AFRolePB myRole;
+ final UserProfilePB userProfile;
+
+ @override
+ Widget build(BuildContext context) {
+ final canDelete = myRole.canDelete && member.email != userProfile.email;
+ final textColor = member.role.isOwner ? Theme.of(context).hintColor : null;
+
+ Widget child = Container(
+ height: 48,
+ padding: const EdgeInsets.symmetric(horizontal: 16.0),
+ child: Row(
+ children: [
+ Expanded(
+ child: FlowyText.medium(
+ member.name,
+ color: textColor,
+ fontSize: 15.0,
+ ),
+ ),
+ Expanded(
+ child: FlowyText.medium(
+ member.role.description,
+ color: textColor,
+ fontSize: 15.0,
+ textAlign: TextAlign.end,
+ ),
+ ),
+ ],
+ ),
+ );
+
+ if (canDelete) {
+ child = Slidable(
+ key: ValueKey(member.email),
+ endActionPane: ActionPane(
+ extentRatio: 1 / 6.0,
+ motion: const ScrollMotion(),
+ children: [
+ CustomSlidableAction(
+ backgroundColor: const Color(0xE5515563),
+ borderRadius: const BorderRadius.only(
+ topLeft: Radius.circular(10),
+ bottomLeft: Radius.circular(10),
+ ),
+ onPressed: (context) {
+ HapticFeedback.mediumImpact();
+ _showDeleteMenu(context);
+ },
+ padding: EdgeInsets.zero,
+ child: const FlowySvg(
+ FlowySvgs.three_dots_s,
+ size: Size.square(24),
+ color: Colors.white,
+ ),
+ ),
+ ],
+ ),
+ child: child,
+ );
+ }
+
+ return child;
+ }
+
+ void _showDeleteMenu(BuildContext context) {
+ final workspaceMemberBloc = context.read();
+ showMobileBottomSheet(
+ context,
+ showDragHandle: true,
+ showDivider: false,
+ useRootNavigator: true,
+ backgroundColor: Theme.of(context).colorScheme.surface,
+ builder: (context) {
+ return FlowyOptionTile.text(
+ text: LocaleKeys.settings_appearance_members_removeFromWorkspace.tr(),
+ height: 52.0,
+ textColor: Theme.of(context).colorScheme.error,
+ leftIcon: FlowySvg(
+ FlowySvgs.trash_s,
+ size: const Size.square(18),
+ color: Theme.of(context).colorScheme.error,
+ ),
+ showTopBorder: false,
+ showBottomBorder: false,
+ onTap: () {
+ workspaceMemberBloc.add(
+ WorkspaceMemberEvent.removeWorkspaceMember(
+ member.email,
+ ),
+ );
+ Navigator.of(context).pop();
+ },
+ );
+ },
+ );
+ }
+}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/workspace/workspace_setting_group.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/workspace/workspace_setting_group.dart
new file mode 100644
index 0000000000..9c2161a4d1
--- /dev/null
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/workspace/workspace_setting_group.dart
@@ -0,0 +1,29 @@
+import 'package:appflowy/generated/locale_keys.g.dart';
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+
+import '../widgets/widgets.dart';
+import 'invite_members_screen.dart';
+
+class WorkspaceSettingGroup extends StatelessWidget {
+ const WorkspaceSettingGroup({
+ super.key,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return MobileSettingGroup(
+ groupTitle: LocaleKeys.settings_appearance_members_label.tr(),
+ settingItemList: [
+ MobileSettingItem(
+ name: LocaleKeys.settings_appearance_members_label.tr(),
+ trailing: const Icon(Icons.chevron_right),
+ onTap: () {
+ context.push(InviteMembersScreen.routeName);
+ },
+ ),
+ ],
+ );
+ }
+}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/widgets/show_flowy_mobile_confirm_dialog.dart b/frontend/appflowy_flutter/lib/mobile/presentation/widgets/show_flowy_mobile_confirm_dialog.dart
index 321632a36a..90bb12120a 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/widgets/show_flowy_mobile_confirm_dialog.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/widgets/show_flowy_mobile_confirm_dialog.dart
@@ -98,12 +98,13 @@ Future showFlowyCupertinoConfirmDialog({
}) {
return showDialog(
context: context ?? AppGlobals.context,
+ barrierColor: Colors.black.withOpacity(0.25),
builder: (context) => CupertinoAlertDialog(
title: FlowyText.medium(
title,
- fontSize: 18,
+ fontSize: 16,
maxLines: 10,
- lineHeight: 1.3,
+ figmaLineHeight: 22.0,
),
actions: [
CupertinoDialogAction(
diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/chat_page.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/chat_page.dart
index fced505f20..bb2c3af521 100644
--- a/frontend/appflowy_flutter/lib/plugins/ai_chat/chat_page.dart
+++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/chat_page.dart
@@ -425,13 +425,14 @@ class _ChatContentPageState extends State<_ChatContentPage> {
},
),
const VSpace(6),
- Opacity(
- opacity: 0.6,
- child: FlowyText(
- LocaleKeys.chat_aiMistakePrompt.tr(),
- fontSize: 12,
+ if (PlatformExtension.isDesktop)
+ Opacity(
+ opacity: 0.6,
+ child: FlowyText(
+ LocaleKeys.chat_aiMistakePrompt.tr(),
+ fontSize: 12,
+ ),
),
- ),
],
);
},
diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_welcome_page.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_welcome_page.dart
index f1ec5d2a7d..5524f1ffbe 100644
--- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_welcome_page.dart
+++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_welcome_page.dart
@@ -73,6 +73,7 @@ class ChatWelcomePage extends StatelessWidget {
const VSpace(8),
Wrap(
direction: Axis.vertical,
+ spacing: isMobile ? 12.0 : 0.0,
children: items
.map(
(i) => WelcomeQuestionWidget(
diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/row/related_row_detail_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/row/related_row_detail_bloc.dart
index 06e1e2b70f..1390d9ff97 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/application/row/related_row_detail_bloc.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/application/row/related_row_detail_bloc.dart
@@ -23,9 +23,9 @@ class RelatedRowDetailPageBloc
@override
Future close() {
state.whenOrNull(
- ready: (databaseController, rowController) {
- rowController.dispose();
- databaseController.dispose();
+ ready: (databaseController, rowController) async {
+ await rowController.dispose();
+ await databaseController.dispose();
},
);
return super.close();
@@ -36,8 +36,8 @@ class RelatedRowDetailPageBloc
event.when(
didInitialize: (databaseController, rowController) {
state.maybeWhen(
- ready: (_, oldRowController) {
- oldRowController.dispose();
+ ready: (_, oldRowController) async {
+ await oldRowController.dispose();
emit(
RelatedRowDetailPageState.ready(
databaseController: databaseController,
diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/row/row_cache.dart b/frontend/appflowy_flutter/lib/plugins/database/application/row/row_cache.dart
index 90f20b2fe7..6f4d886f80 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/application/row/row_cache.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/application/row/row_cache.dart
@@ -81,6 +81,12 @@ class RowCache {
_changedNotifier.receive(const ChangedReason.setInitialRows());
}
+ void setRowMeta(RowMetaPB rowMeta) {
+ final rowInfo = buildGridRow(rowMeta);
+ _rowList.add(rowInfo);
+ _changedNotifier.receive(const ChangedReason.didFetchRow());
+ }
+
void dispose() {
_rowLifeCycle.onRowDisposed();
_changedNotifier.dispose();
@@ -215,7 +221,8 @@ class RowCache {
if (rowInfo == null) {
_loadRow(rowMeta.id);
}
- return _makeCells(rowMeta);
+ final cells = _makeCells(rowMeta);
+ return cells;
}
Future _loadRow(RowId rowId) async {
@@ -277,6 +284,7 @@ class RowChangesetNotifier extends ChangeNotifier {
reorderRows: (_) => notifyListeners(),
reorderSingleRow: (_) => notifyListeners(),
setInitialRows: (_) => notifyListeners(),
+ didFetchRow: (_) => notifyListeners(),
);
}
}
@@ -305,6 +313,7 @@ class ChangedReason with _$ChangedReason {
const factory ChangedReason.update(UpdatedIndexMap indexs) = _Update;
const factory ChangedReason.fieldDidChange() = _FieldDidChange;
const factory ChangedReason.initial() = InitialListState;
+ const factory ChangedReason.didFetchRow() = _DidFetchRow;
const factory ChangedReason.reorderRows() = _ReorderRows;
const factory ChangedReason.reorderSingleRow(
ReorderSingleRowPB reorderRow,
diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/row/row_controller.dart b/frontend/appflowy_flutter/lib/plugins/database/application/row/row_controller.dart
index b34beba275..a52bd66199 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/application/row/row_controller.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/application/row/row_controller.dart
@@ -1,3 +1,5 @@
+import 'package:appflowy/plugins/database/application/row/row_service.dart';
+import 'package:appflowy/plugins/database/domain/row_listener.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
import 'package:flutter/material.dart';
@@ -9,35 +11,60 @@ typedef OnRowChanged = void Function(List, ChangedReason);
class RowController {
RowController({
- required this.rowMeta,
+ required RowMetaPB rowMeta,
required this.viewId,
required RowCache rowCache,
this.groupId,
- }) : _rowCache = rowCache;
+ }) : _rowMeta = rowMeta,
+ _rowCache = rowCache,
+ _rowBackendSvc = RowBackendService(viewId: viewId),
+ _rowListener = RowListener(rowMeta.id) {
+ _rowBackendSvc.initRow(rowMeta.id);
+ _rowListener.start(
+ onMetaChanged: (newRowMeta) {
+ if (_isDisposed) {
+ return;
+ }
+ _rowMeta = newRowMeta;
+ _rowCache.setRowMeta(newRowMeta);
+ },
+ );
+ }
- final RowMetaPB rowMeta;
+ RowMetaPB _rowMeta;
final String? groupId;
final String viewId;
final List _onRowChangedListeners = [];
final RowCache _rowCache;
+ final RowListener _rowListener;
+ final RowBackendService _rowBackendSvc;
+ bool _isDisposed = false;
CellMemCache get cellCache => _rowCache.cellCache;
String get rowId => rowMeta.id;
+ RowMetaPB get rowMeta => _rowMeta;
- List loadData() => _rowCache.loadCells(rowMeta);
+ List loadCells() => _rowCache.loadCells(rowMeta);
void addListener({OnRowChanged? onRowChanged}) {
final fn = _rowCache.addListener(
rowId: rowMeta.id,
- onRowChanged: onRowChanged,
+ onRowChanged: (context, reasons) {
+ if (_isDisposed) {
+ return;
+ }
+ onRowChanged?.call(context, reasons);
+ },
);
// Add the listener to the list so that we can remove it later.
_onRowChangedListeners.add(fn);
}
- void dispose() {
+ Future dispose() async {
+ _isDisposed = true;
+ await _rowListener.stop();
for (final fn in _onRowChangedListeners) {
_rowCache.removeRowListener(fn);
}
diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/row/row_service.dart b/frontend/appflowy_flutter/lib/plugins/database/application/row/row_service.dart
index 1866891336..c5e71ba78b 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/application/row/row_service.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/application/row/row_service.dart
@@ -37,6 +37,14 @@ class RowBackendService {
return DatabaseEventCreateRow(payload).send();
}
+ Future> initRow(RowId rowId) async {
+ final payload = RowIdPB()
+ ..viewId = viewId
+ ..rowId = rowId;
+
+ return DatabaseEventInitRow(payload).send();
+ }
+
Future> createRowBefore(RowId rowId) {
return createRow(
viewId: viewId,
diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/view/view_cache.dart b/frontend/appflowy_flutter/lib/plugins/database/application/view/view_cache.dart
index 77670fb0bb..7ddd3faf11 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/application/view/view_cache.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/application/view/view_cache.dart
@@ -2,7 +2,9 @@ import 'dart:async';
import 'dart:collection';
import 'package:appflowy/plugins/database/application/row/row_service.dart';
+import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/log.dart';
+import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import '../defines.dart';
import '../field/field_controller.dart';
@@ -91,6 +93,17 @@ class DatabaseViewCache {
(reorderRow) => _rowCache.reorderSingleRow(reorderRow),
(err) => Log.error(err),
),
+ onReloadRows: () {
+ final payload = DatabaseViewIdPB(value: viewId);
+ DatabaseEventGetAllRows(payload).send().then((result) {
+ result.fold(
+ (rows) {
+ _rowCache.setInitialRows(rows.items);
+ },
+ (err) => Log.error(err),
+ );
+ });
+ },
);
_rowCache.onRowsChanged(
diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/view/view_listener.dart b/frontend/appflowy_flutter/lib/plugins/database/application/view/view_listener.dart
index 6a41e2f173..1aecbb2767 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/application/view/view_listener.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/application/view/view_listener.dart
@@ -7,85 +7,96 @@ import 'package:appflowy_backend/protobuf/flowy-database2/sort_entities.pb.dart'
import 'package:appflowy_backend/protobuf/flowy-database2/view_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_result/appflowy_result.dart';
-import 'package:flowy_infra/notifier.dart';
-typedef RowsVisibilityNotifierValue
- = FlowyResult;
-
-typedef NumberOfRowsNotifierValue = FlowyResult;
-typedef ReorderAllRowsNotifierValue = FlowyResult, FlowyError>;
-typedef SingleRowNotifierValue = FlowyResult;
+typedef RowsVisibilityCallback = void Function(
+ FlowyResult,
+);
+typedef NumberOfRowsCallback = void Function(
+ FlowyResult,
+);
+typedef ReorderAllRowsCallback = void Function(
+ FlowyResult, FlowyError>,
+);
+typedef SingleRowCallback = void Function(
+ FlowyResult,
+);
class DatabaseViewListener {
DatabaseViewListener({required this.viewId});
final String viewId;
-
- PublishNotifier? _rowsNotifier = PublishNotifier();
- PublishNotifier? _reorderAllRows =
- PublishNotifier();
- PublishNotifier? _reorderSingleRow =
- PublishNotifier();
- PublishNotifier? _rowsVisibility =
- PublishNotifier();
-
DatabaseNotificationListener? _listener;
void start({
- required void Function(NumberOfRowsNotifierValue) onRowsChanged,
- required void Function(ReorderAllRowsNotifierValue) onReorderAllRows,
- required void Function(SingleRowNotifierValue) onReorderSingleRow,
- required void Function(RowsVisibilityNotifierValue) onRowsVisibilityChanged,
+ required NumberOfRowsCallback onRowsChanged,
+ required ReorderAllRowsCallback onReorderAllRows,
+ required SingleRowCallback onReorderSingleRow,
+ required RowsVisibilityCallback onRowsVisibilityChanged,
+ required void Function() onReloadRows,
}) {
- if (_listener != null) {
- _listener?.stop();
- }
+ // Stop any existing listener
+ _listener?.stop();
+ // Initialize the notification listener
_listener = DatabaseNotificationListener(
objectId: viewId,
- handler: _handler,
+ handler: (ty, result) => _handler(
+ ty,
+ result,
+ onRowsChanged,
+ onReorderAllRows,
+ onReorderSingleRow,
+ onRowsVisibilityChanged,
+ onReloadRows,
+ ),
);
-
- _rowsNotifier?.addPublishListener(onRowsChanged);
- _rowsVisibility?.addPublishListener(onRowsVisibilityChanged);
- _reorderAllRows?.addPublishListener(onReorderAllRows);
- _reorderSingleRow?.addPublishListener(onReorderSingleRow);
}
void _handler(
DatabaseNotification ty,
FlowyResult result,
+ NumberOfRowsCallback onRowsChanged,
+ ReorderAllRowsCallback onReorderAllRows,
+ SingleRowCallback onReorderSingleRow,
+ RowsVisibilityCallback onRowsVisibilityChanged,
+ void Function() onReloadRows,
) {
switch (ty) {
case DatabaseNotification.DidUpdateViewRowsVisibility:
result.fold(
- (payload) => _rowsVisibility?.value =
- FlowyResult.success(RowsVisibilityChangePB.fromBuffer(payload)),
- (error) => _rowsVisibility?.value = FlowyResult.failure(error),
+ (payload) => onRowsVisibilityChanged(
+ FlowyResult.success(RowsVisibilityChangePB.fromBuffer(payload)),
+ ),
+ (error) => onRowsVisibilityChanged(FlowyResult.failure(error)),
);
break;
case DatabaseNotification.DidUpdateRow:
result.fold(
- (payload) => _rowsNotifier?.value =
- FlowyResult.success(RowsChangePB.fromBuffer(payload)),
- (error) => _rowsNotifier?.value = FlowyResult.failure(error),
+ (payload) => onRowsChanged(
+ FlowyResult.success(RowsChangePB.fromBuffer(payload)),
+ ),
+ (error) => onRowsChanged(FlowyResult.failure(error)),
);
break;
case DatabaseNotification.DidReorderRows:
result.fold(
- (payload) => _reorderAllRows?.value = FlowyResult.success(
- ReorderAllRowsPB.fromBuffer(payload).rowOrders,
+ (payload) => onReorderAllRows(
+ FlowyResult.success(ReorderAllRowsPB.fromBuffer(payload).rowOrders),
),
- (error) => _reorderAllRows?.value = FlowyResult.failure(error),
+ (error) => onReorderAllRows(FlowyResult.failure(error)),
);
break;
case DatabaseNotification.DidReorderSingleRow:
result.fold(
- (payload) => _reorderSingleRow?.value =
- FlowyResult.success(ReorderSingleRowPB.fromBuffer(payload)),
- (error) => _reorderSingleRow?.value = FlowyResult.failure(error),
+ (payload) => onReorderSingleRow(
+ FlowyResult.success(ReorderSingleRowPB.fromBuffer(payload)),
+ ),
+ (error) => onReorderSingleRow(FlowyResult.failure(error)),
);
break;
+ case DatabaseNotification.ReloadRows:
+ onReloadRows();
+ break;
default:
break;
}
@@ -93,16 +104,6 @@ class DatabaseViewListener {
Future stop() async {
await _listener?.stop();
- _rowsVisibility?.dispose();
- _rowsVisibility = null;
-
- _rowsNotifier?.dispose();
- _rowsNotifier = null;
-
- _reorderAllRows?.dispose();
- _reorderAllRows = null;
-
- _reorderSingleRow?.dispose();
- _reorderSingleRow = null;
+ _listener = null;
}
}
diff --git a/frontend/appflowy_flutter/lib/plugins/database/calendar/application/calendar_event_editor_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/calendar/application/calendar_event_editor_bloc.dart
index 303daff87e..b122d951be 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/calendar/application/calendar_event_editor_bloc.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/calendar/application/calendar_event_editor_bloc.dart
@@ -34,7 +34,7 @@ class CalendarEventEditorBloc
.firstWhere((fieldInfo) => fieldInfo.isPrimary)
.id;
final cells = rowController
- .loadData()
+ .loadCells()
.where(
(cellContext) =>
_filterCellContext(cellContext, primaryFieldId),
@@ -88,7 +88,7 @@ class CalendarEventEditorBloc
@override
Future close() async {
- rowController.dispose();
+ await rowController.dispose();
return super.close();
}
}
diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/application/calculations/calculations_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/application/calculations/calculations_bloc.dart
index e41fa61b2f..a2b80a29df 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/grid/application/calculations/calculations_bloc.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/grid/application/calculations/calculations_bloc.dart
@@ -39,11 +39,13 @@ class CalculationsBloc extends Bloc {
_startListening();
await _getAllCalculations();
- add(
- CalculationsEvent.didReceiveFieldUpdate(
- _fieldController.fieldInfos,
- ),
- );
+ if (!isClosed) {
+ add(
+ CalculationsEvent.didReceiveFieldUpdate(
+ _fieldController.fieldInfos,
+ ),
+ );
+ }
},
didReceiveFieldUpdate: (fields) async {
emit(
@@ -131,6 +133,10 @@ class CalculationsBloc extends Bloc {
Future _getAllCalculations() async {
final calculationsOrFailure = await _calculationsService.getCalculations();
+ if (isClosed) {
+ return;
+ }
+
final RepeatedCalculationsPB? calculations =
calculationsOrFailure.fold((s) => s, (e) => null);
if (calculations != null) {
diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/application/row/row_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/application/row/row_bloc.dart
index a0c0467b95..69feda410c 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/grid/application/row/row_bloc.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/grid/application/row/row_bloc.dart
@@ -36,7 +36,7 @@ class RowBloc extends Bloc {
@override
Future close() async {
- _rowController.dispose();
+ await _rowController.dispose();
return super.close();
}
@@ -82,7 +82,7 @@ class RowBloc extends Bloc {
void _init() {
add(
RowEvent.didReceiveCells(
- _rowController.loadData(),
+ _rowController.loadCells(),
const ChangedReason.setInitialRows(),
),
);
diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/application/row/row_detail_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/application/row/row_detail_bloc.dart
index 0d655a840b..5c25fc851f 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/grid/application/row/row_detail_bloc.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/grid/application/row/row_detail_bloc.dart
@@ -29,7 +29,7 @@ class RowDetailBloc extends Bloc {
@override
Future close() async {
- rowController.dispose();
+ await rowController.dispose();
return super.close();
}
@@ -125,7 +125,7 @@ class RowDetailBloc extends Bloc {
}
void _init() {
- allCells.addAll(rowController.loadData());
+ allCells.addAll(rowController.loadCells());
int numHiddenFields = 0;
final visibleCells = [];
for (final cell in allCells) {
diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/grid_page.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/grid_page.dart
index 50b67e7a8f..b2e873e67e 100755
--- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/grid_page.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/grid_page.dart
@@ -9,7 +9,6 @@ import 'package:appflowy/workspace/application/action_navigation/navigation_acti
import 'package:appflowy/workspace/application/view/view_bloc.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
-import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_scrollview.dart';
@@ -154,6 +153,7 @@ class _GridPageState extends State {
finish: (result) => result.successOrFail.fold(
(_) => GridShortcuts(
child: GridPageContent(
+ key: ValueKey(widget.view.id),
view: widget.view,
),
),
@@ -331,33 +331,10 @@ class _GridRowsState extends State<_GridRows> {
BuildContext context,
GridState state,
) {
- final children = state.rowInfos.mapIndexed((index, rowInfo) {
- return _renderRow(
- context,
- rowInfo.rowId,
- isDraggable: state.reorderable,
- index: index,
- );
- }).toList()
- ..add(const GridRowBottomBar(key: Key('grid_footer')));
-
- if (showFloatingCalculations) {
- children.add(
- const SizedBox(
- key: Key('calculations_bottom_padding'),
- height: 36,
- ),
- );
- } else {
- children.add(
- GridCalculationsRow(
- key: const Key('grid_calculations'),
- viewId: widget.viewId,
- ),
- );
- }
-
- children.add(const SizedBox(key: Key('footer_padding'), height: 10));
+ // 1. GridRowBottomBar
+ // 2. GridCalculationsRow
+ // 3. Footer Padding
+ final itemCount = state.rowInfos.length + 3;
return Stack(
children: [
@@ -381,8 +358,37 @@ class _GridRowsState extends State<_GridRows> {
.add(GridEvent.moveRow(fromIndex, toIndex));
}
},
- itemCount: children.length,
- itemBuilder: (context, index) => children[index],
+ itemCount: itemCount,
+ itemBuilder: (context, index) {
+ if (index < state.rowInfos.length) {
+ return _renderRow(
+ context,
+ state.rowInfos[index].rowId,
+ isDraggable: state.reorderable,
+ index: index,
+ );
+ }
+
+ if (index == state.rowInfos.length) {
+ return const GridRowBottomBar(key: Key('grid_footer'));
+ }
+
+ if (index == state.rowInfos.length + 1) {
+ if (showFloatingCalculations) {
+ return const SizedBox(
+ key: Key('calculations_bottom_padding'),
+ height: 36,
+ );
+ } else {
+ return GridCalculationsRow(
+ key: const Key('grid_calculations'),
+ viewId: widget.viewId,
+ );
+ }
+ }
+
+ return const SizedBox(key: Key('footer_padding'), height: 10);
+ },
),
),
if (showFloatingCalculations) ...[
diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/layout/sizes.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/layout/sizes.dart
index 2b1e558569..88facd39a7 100755
--- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/layout/sizes.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/layout/sizes.dart
@@ -6,6 +6,7 @@ class GridSize {
static double get scrollBarSize => 8 * scale;
static double get headerHeight => 40 * scale;
+ static double get buttonHeight => 38 * scale;
static double get footerHeight => 40 * scale;
static double get horizontalHeaderPadding =>
PlatformExtension.isDesktop ? 40 * scale : 16 * scale;
diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/row/mobile_row.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/row/mobile_row.dart
index b6817fc848..f4e9d0c751 100755
--- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/row/mobile_row.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/row/mobile_row.dart
@@ -81,7 +81,7 @@ class _MobileGridRowState extends State {
@override
Future dispose() async {
- _rowController.dispose();
+ await _rowController.dispose();
super.dispose();
}
}
diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/row/row.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/row/row.dart
index fd52ec727e..19a5da2438 100755
--- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/row/row.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/row/row.dart
@@ -188,8 +188,14 @@ class _RowMenuButtonState extends State {
richTooltipText: widget.isDragEnabled
? TextSpan(
children: [
- TextSpan(text: '${LocaleKeys.tooltip_dragRow.tr()}\n'),
- TextSpan(text: LocaleKeys.tooltip_openMenu.tr()),
+ TextSpan(
+ text: '${LocaleKeys.tooltip_dragRow.tr()}\n',
+ style: context.tooltipTextStyle(),
+ ),
+ TextSpan(
+ text: LocaleKeys.tooltip_openMenu.tr(),
+ style: context.tooltipTextStyle(),
+ ),
],
)
: null,
diff --git a/frontend/appflowy_flutter/lib/plugins/database/tab_bar/desktop/setting_menu.dart b/frontend/appflowy_flutter/lib/plugins/database/tab_bar/desktop/setting_menu.dart
index 5b66c3a149..ad08d6b8e2 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/tab_bar/desktop/setting_menu.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/tab_bar/desktop/setting_menu.dart
@@ -57,7 +57,7 @@ class _DatabaseViewSettingContent extends StatelessWidget {
builder: (context, state) {
return Padding(
padding: EdgeInsets.symmetric(
- horizontal: GridSize.horizontalHeaderPadding,
+ horizontal: GridSize.horizontalHeaderPadding + 40,
),
child: DecoratedBox(
decoration: BoxDecoration(
diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_summary_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_summary_cell.dart
index a900cf62fb..1e709bdeb9 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_summary_cell.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_summary_cell.dart
@@ -13,42 +13,54 @@ class MobileRowDetailSummaryCellSkin extends IEditableSummaryCellSkin {
FocusNode focusNode,
TextEditingController textEditingController,
) {
- return Column(
- children: [
- TextField(
- controller: textEditingController,
- readOnly: true,
- focusNode: focusNode,
- onEditingComplete: () => focusNode.unfocus(),
- onSubmitted: (_) => focusNode.unfocus(),
- style: Theme.of(context).textTheme.bodyMedium,
- textInputAction: TextInputAction.done,
- maxLines: null,
- minLines: 1,
- decoration: InputDecoration(
- contentPadding: GridSize.cellContentInsets,
- border: InputBorder.none,
- focusedBorder: InputBorder.none,
- enabledBorder: InputBorder.none,
- errorBorder: InputBorder.none,
- disabledBorder: InputBorder.none,
- isDense: true,
- ),
+ return Container(
+ decoration: BoxDecoration(
+ border: Border.fromBorderSide(
+ BorderSide(color: Theme.of(context).colorScheme.outline),
),
- Row(
- children: [
- const Spacer(),
- Padding(
- padding: const EdgeInsets.all(8.0),
- child: SummaryCellAccessory(
- viewId: bloc.cellController.viewId,
- fieldId: bloc.cellController.fieldId,
- rowId: bloc.cellController.rowId,
- ),
+ borderRadius: const BorderRadius.all(Radius.circular(14)),
+ ),
+ padding: const EdgeInsets.symmetric(
+ horizontal: 4,
+ vertical: 2,
+ ),
+ child: Column(
+ children: [
+ TextField(
+ controller: textEditingController,
+ readOnly: true,
+ focusNode: focusNode,
+ onEditingComplete: () => focusNode.unfocus(),
+ onSubmitted: (_) => focusNode.unfocus(),
+ style: Theme.of(context).textTheme.bodyMedium,
+ textInputAction: TextInputAction.done,
+ maxLines: null,
+ minLines: 1,
+ decoration: InputDecoration(
+ contentPadding: GridSize.cellContentInsets,
+ border: InputBorder.none,
+ focusedBorder: InputBorder.none,
+ enabledBorder: InputBorder.none,
+ errorBorder: InputBorder.none,
+ disabledBorder: InputBorder.none,
+ isDense: true,
),
- ],
- ),
- ],
+ ),
+ Row(
+ children: [
+ const Spacer(),
+ Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: SummaryCellAccessory(
+ viewId: bloc.cellController.viewId,
+ fieldId: bloc.cellController.fieldId,
+ rowId: bloc.cellController.rowId,
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
);
}
}
diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_translate_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_translate_cell.dart
index 84af6c7062..a1e4b4bf29 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_translate_cell.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_translate_cell.dart
@@ -13,42 +13,54 @@ class MobileRowDetailTranslateCellSkin extends IEditableTranslateCellSkin {
FocusNode focusNode,
TextEditingController textEditingController,
) {
- return Column(
- children: [
- TextField(
- readOnly: true,
- controller: textEditingController,
- focusNode: focusNode,
- onEditingComplete: () => focusNode.unfocus(),
- onSubmitted: (_) => focusNode.unfocus(),
- style: Theme.of(context).textTheme.bodyMedium,
- textInputAction: TextInputAction.done,
- maxLines: null,
- minLines: 1,
- decoration: InputDecoration(
- contentPadding: GridSize.cellContentInsets,
- border: InputBorder.none,
- focusedBorder: InputBorder.none,
- enabledBorder: InputBorder.none,
- errorBorder: InputBorder.none,
- disabledBorder: InputBorder.none,
- isDense: true,
- ),
+ return Container(
+ decoration: BoxDecoration(
+ border: Border.fromBorderSide(
+ BorderSide(color: Theme.of(context).colorScheme.outline),
),
- Row(
- children: [
- const Spacer(),
- Padding(
- padding: const EdgeInsets.all(8.0),
- child: TranslateCellAccessory(
- viewId: bloc.cellController.viewId,
- fieldId: bloc.cellController.fieldId,
- rowId: bloc.cellController.rowId,
- ),
+ borderRadius: const BorderRadius.all(Radius.circular(14)),
+ ),
+ padding: const EdgeInsets.symmetric(
+ horizontal: 4,
+ vertical: 2,
+ ),
+ child: Column(
+ children: [
+ TextField(
+ readOnly: true,
+ controller: textEditingController,
+ focusNode: focusNode,
+ onEditingComplete: () => focusNode.unfocus(),
+ onSubmitted: (_) => focusNode.unfocus(),
+ style: Theme.of(context).textTheme.bodyMedium,
+ textInputAction: TextInputAction.done,
+ maxLines: null,
+ minLines: 1,
+ decoration: InputDecoration(
+ contentPadding: GridSize.cellContentInsets,
+ border: InputBorder.none,
+ focusedBorder: InputBorder.none,
+ enabledBorder: InputBorder.none,
+ errorBorder: InputBorder.none,
+ disabledBorder: InputBorder.none,
+ isDense: true,
),
- ],
- ),
- ],
+ ),
+ Row(
+ children: [
+ const Spacer(),
+ Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: TranslateCellAccessory(
+ viewId: bloc.cellController.viewId,
+ fieldId: bloc.cellController.fieldId,
+ rowId: bloc.cellController.rowId,
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
);
}
}
diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_property.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_property.dart
index fe08b53ab0..60dc940cea 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_property.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_property.dart
@@ -1,8 +1,5 @@
import 'dart:io';
-import 'package:flutter/foundation.dart';
-import 'package:flutter/material.dart';
-
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database/application/cell/cell_controller.dart';
@@ -21,10 +18,11 @@ import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../cell/editable_cell_builder.dart';
-
import 'accessory/cell_accessory.dart';
/// Display the row properties in a list. Only used in [RowDetailPage].
@@ -165,6 +163,7 @@ class _PropertyCellState extends State<_PropertyCell> {
svg: FlowySvgs.drag_element_s,
richMessage: TextSpan(
text: LocaleKeys.grid_rowPage_fieldDragElementTooltip.tr(),
+ style: context.tooltipTextStyle(),
),
),
),
diff --git a/frontend/appflowy_flutter/lib/plugins/database_document/presentation/database_document_title.dart b/frontend/appflowy_flutter/lib/plugins/database_document/presentation/database_document_title.dart
index 8a71e26efa..d509aa2f25 100644
--- a/frontend/appflowy_flutter/lib/plugins/database_document/presentation/database_document_title.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database_document/presentation/database_document_title.dart
@@ -1,15 +1,12 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
-import 'package:appflowy/plugins/base/emoji/emoji_text.dart';
import 'package:appflowy/plugins/database/application/cell/bloc/text_cell_bloc.dart';
import 'package:appflowy/plugins/database/application/cell/cell_controller.dart';
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dart';
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/text.dart';
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart';
-import 'package:appflowy/startup/tasks/app_window_size_manager.dart';
-import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
-import 'package:appflowy/workspace/application/view/view_listener.dart';
+import 'package:appflowy/workspace/presentation/widgets/view_title_bar.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:collection/collection.dart';
@@ -47,20 +44,16 @@ class ViewTitleBarWithRow extends StatelessWidget {
if (state.ancestors.isEmpty) {
return const SizedBox.shrink();
}
- const maxWidth = WindowSizeManager.minWindowWidth - 200;
- return LayoutBuilder(
- builder: (context, constraints) {
- return Visibility(
- visible: maxWidth < constraints.maxWidth,
- // if the width is too small, only show one view title bar without the ancestors
- replacement: _buildRowName(),
- child: Row(
- // refresh the view title bar when the ancestors changed
- key: ValueKey(state.ancestors.hashCode),
- children: _buildViewTitles(state.ancestors),
- ),
- );
- },
+ return SingleChildScrollView(
+ scrollDirection: Axis.horizontal,
+ child: SizedBox(
+ height: 24,
+ child: Row(
+ // refresh the view title bar when the ancestors changed
+ key: ValueKey(state.ancestors.hashCode),
+ children: _buildViewTitles(state.ancestors),
+ ),
+ ),
);
},
),
@@ -71,16 +64,22 @@ class ViewTitleBarWithRow extends StatelessWidget {
// if the level is too deep, only show the root view, the database view and the row
return views.length > 2
? [
- _buildViewButton(views.first),
- const FlowyText.regular('/'),
- const FlowyText.regular(' ... /'),
+ _buildViewButton(views[1]),
+ const FlowySvg(FlowySvgs.title_bar_divider_s),
+ const FlowyText.regular(' ... '),
+ const FlowySvg(FlowySvgs.title_bar_divider_s),
_buildViewButton(views.last),
- const FlowyText.regular('/'),
+ const FlowySvg(FlowySvgs.title_bar_divider_s),
_buildRowName(),
]
: [
...views
- .map((e) => [_buildViewButton(e), const FlowyText.regular('/')])
+ .map(
+ (e) => [
+ _buildViewButton(e),
+ const FlowySvg(FlowySvgs.title_bar_divider_s),
+ ],
+ )
.flattened,
_buildRowName(),
];
@@ -89,9 +88,9 @@ class ViewTitleBarWithRow extends StatelessWidget {
Widget _buildViewButton(ViewPB view) {
return FlowyTooltip(
message: view.name,
- child: _ViewTitle(
+ child: ViewTitle(
view: view,
- behavior: _ViewTitleBehavior.uneditable,
+ behavior: ViewTitleBehavior.uneditable,
onUpdated: () {},
),
);
@@ -180,11 +179,14 @@ class _TitleSkin extends IEditableTextCellSkin {
onTap: () {},
text: Row(
children: [
- EmojiText(
- emoji: state.icon ?? "",
- fontSize: 18.0,
- ),
- const HSpace(2.0),
+ if (state.icon != null) ...[
+ FlowyText.emoji(
+ state.icon!,
+ fontSize: 14.0,
+ figmaLineHeight: 18.0,
+ ),
+ const HSpace(4.0),
+ ],
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 180),
child: FlowyText.regular(
@@ -204,106 +206,6 @@ class _TitleSkin extends IEditableTextCellSkin {
}
}
-enum _ViewTitleBehavior {
- editable,
- uneditable,
-}
-
-class _ViewTitle extends StatefulWidget {
- const _ViewTitle({
- required this.view,
- this.behavior = _ViewTitleBehavior.editable,
- required this.onUpdated,
- }) : maxTitleWidth = 180;
-
- final ViewPB view;
- final _ViewTitleBehavior behavior;
- final double maxTitleWidth;
- final VoidCallback onUpdated;
-
- @override
- State<_ViewTitle> createState() => _ViewTitleState();
-}
-
-class _ViewTitleState extends State<_ViewTitle> {
- late final viewListener = ViewListener(viewId: widget.view.id);
-
- String name = '';
- String icon = '';
-
- @override
- void initState() {
- super.initState();
-
- name = widget.view.name.isEmpty
- ? LocaleKeys.document_title_placeholder.tr()
- : widget.view.name;
- icon = widget.view.icon.value;
-
- viewListener.start(
- onViewUpdated: (view) {
- if (name != view.name || icon != view.icon.value) {
- widget.onUpdated();
- }
- setState(() {
- name = view.name.isEmpty
- ? LocaleKeys.document_title_placeholder.tr()
- : view.name;
- icon = view.icon.value;
- });
- },
- );
- }
-
- @override
- void dispose() {
- viewListener.stop();
-
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- // root view
- if (widget.view.parentViewId.isEmpty) {
- return Row(
- children: [
- FlowyText.regular(name),
- const HSpace(4.0),
- ],
- );
- }
-
- final child = Row(
- children: [
- EmojiText(
- emoji: icon,
- fontSize: 18.0,
- ),
- const HSpace(2.0),
- ConstrainedBox(
- constraints: BoxConstraints(
- maxWidth: widget.maxTitleWidth,
- ),
- child: FlowyText.regular(
- name,
- overflow: TextOverflow.ellipsis,
- ),
- ),
- ],
- );
-
- return Listener(
- onPointerDown: (_) => context.read().openPlugin(widget.view),
- child: FlowyButton(
- useIntrinsicWidth: true,
- onTap: () {},
- text: child,
- ),
- );
- }
-}
-
class RenameRowPopover extends StatefulWidget {
const RenameRowPopover({
super.key,
diff --git a/frontend/appflowy_flutter/lib/plugins/document/application/document_collaborators_bloc.dart b/frontend/appflowy_flutter/lib/plugins/document/application/document_collaborators_bloc.dart
index 7e5e4eb528..b6352b0430 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/application/document_collaborators_bloc.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/application/document_collaborators_bloc.dart
@@ -39,6 +39,10 @@ class DocumentCollaboratorsBloc
if (userProfile != null) {
_listener.start(
onDocAwarenessUpdate: (states) {
+ if (isClosed) {
+ return;
+ }
+
add(
DocumentCollaboratorsEvent.update(
userProfile,
diff --git a/frontend/appflowy_flutter/lib/plugins/document/document.dart b/frontend/appflowy_flutter/lib/plugins/document/document.dart
index aa0154bf92..ec4dde94ae 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/document.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/document.dart
@@ -2,6 +2,7 @@ library document_plugin;
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
+import 'package:appflowy/mobile/presentation/presentation.dart';
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
import 'package:appflowy/plugins/document/document_page.dart';
import 'package:appflowy/plugins/document/presentation/document_collaborators.dart';
@@ -118,6 +119,8 @@ class DocumentPluginWidgetBuilder extends PluginWidgetBuilder
}
});
+ final fixedTitle = data?[MobileDocumentScreen.viewFixedTitle];
+
return BlocProvider.value(
value: bloc,
child: BlocBuilder(
@@ -126,6 +129,7 @@ class DocumentPluginWidgetBuilder extends PluginWidgetBuilder
view: view,
onDeleted: () => context.onDeleted?.call(view, deletedViewIndex),
initialSelection: initialSelection,
+ fixedTitle: fixedTitle,
),
),
);
diff --git a/frontend/appflowy_flutter/lib/plugins/document/document_page.dart b/frontend/appflowy_flutter/lib/plugins/document/document_page.dart
index 292b399731..d835a7c00b 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/document_page.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/document_page.dart
@@ -1,5 +1,3 @@
-import 'package:flutter/material.dart';
-
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
import 'package:appflowy/plugins/document/application/document_bloc.dart';
@@ -26,6 +24,7 @@ import 'package:cross_file/cross_file.dart';
import 'package:desktop_drop/desktop_drop.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/widget/error_page.dart';
+import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:provider/provider.dart';
@@ -42,11 +41,13 @@ class DocumentPage extends StatefulWidget {
required this.view,
required this.onDeleted,
this.initialSelection,
+ this.fixedTitle,
});
final ViewPB view;
final VoidCallback onDeleted;
final Selection? initialSelection;
+ final String? fixedTitle;
@override
State createState() => _DocumentPageState();
@@ -103,6 +104,7 @@ class _DocumentPageState extends State
BlocProvider.value(value: documentBloc),
],
child: BlocBuilder(
+ buildWhen: _shouldRebuildDocument,
builder: (context, state) {
if (state.isLoading) {
return const Center(child: CircularProgressIndicator.adaptive());
@@ -195,13 +197,15 @@ class _DocumentPageState extends State
final isLocalMode = context.read().isLocalMode;
final List imageFiles = [];
- final List otherfiles = [];
+ final List otherFiles = [];
+
for (final file in details.files) {
+ final fileName = file.name.toLowerCase();
if (file.mimeType?.startsWith('image/') ??
- false || imgExtensionRegex.hasMatch(file.name)) {
+ false || imgExtensionRegex.hasMatch(fileName)) {
imageFiles.add(file);
} else {
- otherfiles.add(file);
+ otherFiles.add(file);
}
}
@@ -213,7 +217,7 @@ class _DocumentPageState extends State
);
await editorState!.dropFiles(
data.dropTarget!,
- otherfiles,
+ otherFiles,
widget.view.id,
isLocalMode,
);
@@ -261,6 +265,7 @@ class _DocumentPageState extends State
if (PlatformExtension.isMobile) {
return DocumentImmersiveCover(
+ fixedTitle: widget.fixedTitle,
view: widget.view,
userProfilePB: userProfilePB,
);
@@ -308,4 +313,31 @@ class _DocumentPageState extends State
}
}
}
+
+ bool _shouldRebuildDocument(DocumentState previous, DocumentState current) {
+ // only rebuild the document page when the below fields are changed
+ // this is to prevent unnecessary rebuilds
+ //
+ // If you confirm the newly added fields should be rebuilt, please update
+ // this function.
+ if (previous.editorState != current.editorState) {
+ return true;
+ }
+
+ if (previous.forceClose != current.forceClose ||
+ previous.isDeleted != current.isDeleted) {
+ return true;
+ }
+
+ if (previous.userProfilePB != current.userProfilePB) {
+ return true;
+ }
+
+ if (previous.isLoading != current.isLoading ||
+ previous.error != current.error) {
+ return true;
+ }
+
+ return false;
+ }
}
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart
index 87eec5b47c..731a0b6d6d 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart
@@ -419,7 +419,10 @@ class _AppFlowyEditorPageState extends State {
imageSlashMenuItem,
bulletedListSlashMenuItem,
numberedListSlashMenuItem,
+ todoListSlashMenuItem,
+ dividerSlashMenuItem,
quoteSlashMenuItem,
+ tableSlashMenuItem,
referencedDocSlashMenuItem,
gridSlashMenuItem(documentBloc),
referencedGridSlashMenuItem,
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_add_button.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_add_button.dart
index b58b0a5646..6d01ed5f1b 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_add_button.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_add_button.dart
@@ -5,6 +5,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_button.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -31,16 +32,19 @@ class BlockAddButton extends StatelessWidget {
children: [
TextSpan(
text: LocaleKeys.blockActions_addBelowTooltip.tr(),
+ style: context.tooltipTextStyle(),
),
const TextSpan(text: '\n'),
TextSpan(
text: Platform.isMacOS
? LocaleKeys.blockActions_addAboveMacCmd.tr()
: LocaleKeys.blockActions_addAboveCmd.tr(),
+ style: context.tooltipTextStyle(),
),
const TextSpan(text: ' '),
TextSpan(
text: LocaleKeys.blockActions_addAboveTooltip.tr(),
+ style: context.tooltipTextStyle(),
),
],
),
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_button.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_button.dart
index e6a88bc4a8..0822d04db7 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_button.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_button.dart
@@ -21,7 +21,6 @@ class BlockActionButton extends StatelessWidget {
Widget build(BuildContext context) {
return Align(
child: FlowyTooltip(
- preferBelow: false,
richMessage: richMessage,
child: MouseRegion(
cursor: Platform.isWindows
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_option_button.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_option_button.dart
index a5617a5558..205725b81a 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_option_button.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_option_button.dart
@@ -7,6 +7,7 @@ import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -67,11 +68,14 @@ class BlockOptionButton extends StatelessWidget {
controller.close();
}
},
- buildChild: (controller) => _buildOptionButton(controller),
+ buildChild: (controller) => _buildOptionButton(context, controller),
);
}
- Widget _buildOptionButton(PopoverController controller) {
+ Widget _buildOptionButton(
+ BuildContext context,
+ PopoverController controller,
+ ) {
return BlockActionButton(
svg: FlowySvgs.drag_element_s,
richMessage: TextSpan(
@@ -79,9 +83,11 @@ class BlockOptionButton extends StatelessWidget {
TextSpan(
// todo: customize the color to highlight the text.
text: LocaleKeys.document_plugins_optionAction_click.tr(),
+ style: context.tooltipTextStyle(),
),
TextSpan(
text: LocaleKeys.document_plugins_optionAction_toOpenMenu.tr(),
+ style: context.tooltipTextStyle(),
),
],
),
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart
index 436de2c601..1dde980f03 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart
@@ -100,9 +100,7 @@ class ClipboardService {
for (final item in reader.items) {
final availableFormats = await item.rawReader!.getAvailableFormats();
- Log.debug(
- 'availableFormats: $availableFormats',
- );
+ Log.info('availableFormats: $availableFormats');
}
final plainText = await reader.readValue(Formats.plainText);
@@ -115,6 +113,8 @@ class ClipboardService {
image = ('jpeg', await reader.readFile(Formats.jpeg));
} else if (reader.canProvide(Formats.gif)) {
image = ('gif', await reader.readFile(Formats.gif));
+ } else if (reader.canProvide(Formats.webp)) {
+ image = ('webp', await reader.readFile(Formats.webp));
}
return ClipboardServiceData(
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_paste_command.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_paste_command.dart
index 067d6b766c..2e0cef8b8b 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_paste_command.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_paste_command.dart
@@ -76,6 +76,7 @@ CommandShortcutEventHandler _pasteCommandHandler = (editorState) {
image.$1,
image.$2!,
documentId,
+ selection: selection,
);
if (result) {
Log.info('Pasted image');
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_image.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_image.dart
index 989798dcf2..57ebe69fc6 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_image.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_image.dart
@@ -9,7 +9,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/image/imag
import 'package:appflowy/shared/patterns/common_patterns.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/settings/application_data_storage.dart';
-import 'package:appflowy/workspace/presentation/home/toast.dart';
+import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
import 'package:cross_file/cross_file.dart';
@@ -23,6 +23,7 @@ extension PasteFromImage on EditorState {
'png',
'jpeg',
'gif',
+ 'webp',
];
Future dropImages(
@@ -34,7 +35,7 @@ extension PasteFromImage on EditorState {
final imageFiles = files.where(
(file) =>
file.mimeType?.startsWith('image/') ??
- false || imgExtensionRegex.hasMatch(file.name),
+ false || imgExtensionRegex.hasMatch(file.name.toLowerCase()),
);
for (final file in imageFiles) {
@@ -64,18 +65,26 @@ extension PasteFromImage on EditorState {
Future pasteImage(
String format,
Uint8List imageBytes,
- String documentId,
- ) async {
- if (!supportedImageFormats.contains(format)) {
- return false;
- }
-
+ String documentId, {
+ Selection? selection,
+ }) async {
final context = document.root.context;
if (context == null) {
return false;
}
+ if (!supportedImageFormats.contains(format)) {
+ Log.info('unsupported format: $format');
+ if (PlatformExtension.isMobile) {
+ showToastNotification(
+ context,
+ message: LocaleKeys.document_imageBlock_error_invalidImageFormat.tr(),
+ );
+ }
+ return false;
+ }
+
final isLocalMode = context.read().isLocalMode;
final path = await getIt().getPath();
@@ -105,9 +114,9 @@ extension PasteFromImage on EditorState {
final errorMessage = result.$2;
if (errorMessage != null && context.mounted) {
- showSnackBarMessage(
+ showToastNotification(
context,
- errorMessage,
+ message: errorMessage,
);
return false;
}
@@ -116,7 +125,7 @@ extension PasteFromImage on EditorState {
}
if (path != null) {
- await insertImageNode(path);
+ await insertImageNode(path, selection: selection);
}
await File(copyToPath).delete();
@@ -124,13 +133,55 @@ extension PasteFromImage on EditorState {
} catch (e) {
Log.error('cannot copy image file', e);
if (context.mounted) {
- showSnackBarMessage(
+ showToastNotification(
context,
- LocaleKeys.document_imageBlock_error_invalidImage.tr(),
+ message: LocaleKeys.document_imageBlock_error_invalidImage.tr(),
);
}
}
return false;
}
+
+ Future insertImageNode(
+ String src, {
+ Selection? selection,
+ }) async {
+ selection ??= this.selection;
+ if (selection == null || !selection.isCollapsed) {
+ return;
+ }
+ final node = getNodeAtPath(selection.end.path);
+ if (node == null) {
+ return;
+ }
+ final transaction = this.transaction;
+ // if the current node is empty paragraph, replace it with image node
+ if (node.type == ParagraphBlockKeys.type &&
+ (node.delta?.isEmpty ?? false)) {
+ transaction
+ ..insertNode(
+ node.path,
+ imageNode(
+ url: src,
+ ),
+ )
+ ..deleteNode(node);
+ } else {
+ transaction.insertNode(
+ node.path.next,
+ imageNode(
+ url: src,
+ ),
+ );
+ }
+
+ transaction.afterSelection = Selection.collapsed(
+ Position(
+ path: node.path.next,
+ ),
+ );
+
+ return apply(transaction);
+ }
}
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart
index d1b550eded..3c04ce4301 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart
@@ -34,10 +34,12 @@ class DocumentImmersiveCover extends StatefulWidget {
super.key,
required this.view,
required this.userProfilePB,
+ this.fixedTitle,
});
final ViewPB view;
final UserProfilePB userProfilePB;
+ final String? fixedTitle;
@override
State createState() => _DocumentImmersiveCoverState();
@@ -143,6 +145,18 @@ class _DocumentImmersiveCoverState extends State {
fontFamily = getGoogleFontSafely(documentFontFamily).fontFamily;
}
+ if (widget.fixedTitle != null) {
+ return FlowyText(
+ widget.fixedTitle!,
+ fontSize: 28.0,
+ fontWeight: FontWeight.w700,
+ fontFamily: fontFamily,
+ color:
+ state.cover.isNone || state.cover.isPresets ? null : Colors.white,
+ overflow: TextOverflow.ellipsis,
+ );
+ }
+
return AutoSizeTextField(
controller: textEditingController,
focusNode: focusNode,
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_util.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_util.dart
index cedeaec2ee..f4066a94f2 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_util.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_util.dart
@@ -3,7 +3,6 @@ import 'dart:io';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/application/document_service.dart';
import 'package:appflowy/startup/startup.dart';
-import 'package:appflowy/util/file_extension.dart';
import 'package:appflowy/workspace/application/settings/application_data_storage.dart';
import 'package:appflowy_backend/dispatch/error.dart';
import 'package:appflowy_backend/log.dart';
@@ -39,14 +38,6 @@ Future<(String? path, String? errorMessage)> saveFileToCloudStorage(
String localFilePath,
String documentId,
) async {
- final size = localFilePath.fileSize;
- if (size == null || size > 10 * 1024 * 1024) {
- // 10MB
- return (
- null,
- LocaleKeys.document_plugins_file_fileTooBigError.tr(),
- );
- }
final documentService = DocumentService();
Log.debug("Uploading file from local path: $localFilePath");
final result = await documentService.uploadFile(
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_util.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_util.dart
index 6e650f1bf8..e7b818ead7 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_util.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_util.dart
@@ -7,7 +7,6 @@ import 'package:appflowy/plugins/document/application/prelude.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/common.dart';
import 'package:appflowy/shared/custom_image_cache_manager.dart';
import 'package:appflowy/startup/startup.dart';
-import 'package:appflowy/util/file_extension.dart';
import 'package:appflowy/workspace/application/settings/application_data_storage.dart';
import 'package:appflowy/workspace/presentation/home/toast.dart';
import 'package:appflowy_backend/dispatch/error.dart';
@@ -47,14 +46,6 @@ Future<(String? path, String? errorMessage)> saveImageToCloudStorage(
String localImagePath,
String documentId,
) async {
- final size = localImagePath.fileSize;
- if (size == null || size > 10 * 1024 * 1024) {
- // 10MB
- return (
- null,
- LocaleKeys.document_imageBlock_uploadImageErrorImageSizeTooBig.tr(),
- );
- }
final documentService = DocumentService();
Log.debug("Uploading image local path: $localImagePath");
final result = await documentService.uploadFile(
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/upload_image_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/upload_image_menu.dart
index 70b54e0da1..6c4a8dcfd3 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/upload_image_menu.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/upload_image_menu.dart
@@ -121,38 +121,36 @@ class _UploadImageMenuState extends State {
final type = values[currentTabIndex];
switch (type) {
case UploadImageType.local:
- return Column(
- children: [
- Padding(
- padding: const EdgeInsets.all(8.0),
- child: Container(
- alignment: Alignment.center,
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(8),
- border: Border.all(
- color: Theme.of(context).colorScheme.outline,
- ),
- ),
- constraints: constraints,
- child: Column(
- children: [
- UploadImageFileWidget(
- allowMultipleImages: widget.allowMultipleImages,
- onPickFiles: widget.onSelectedLocalImages,
- ),
- ],
+ Widget child = UploadImageFileWidget(
+ allowMultipleImages: widget.allowMultipleImages,
+ onPickFiles: widget.onSelectedLocalImages,
+ );
+ if (PlatformExtension.isDesktop) {
+ child = Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Container(
+ alignment: Alignment.center,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(8),
+ border: Border.all(
+ color: Theme.of(context).colorScheme.outline,
),
),
+ constraints: constraints,
+ child: child,
),
- // if (widget.limitMaximumImageSize) ...[
- // FlowyText(
- // LocaleKeys.document_imageBlock_maximumImageSize.tr(),
- // fontSize: 10.0,
- // color: Theme.of(context).hintColor,
- // ),
- // ],
- ],
- );
+ );
+ } else {
+ child = Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 8.0,
+ vertical: 12.0,
+ ),
+ child: child,
+ );
+ }
+ return child;
+
case UploadImageType.url:
return Container(
padding: const EdgeInsets.all(8.0),
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/embed_image_url_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/embed_image_url_widget.dart
index d84dcab318..4fa4ec7319 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/embed_image_url_widget.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/embed_image_url_widget.dart
@@ -1,5 +1,6 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/shared/patterns/common_patterns.dart';
+import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
@@ -22,14 +23,27 @@ class _EmbedImageUrlWidgetState extends State {
@override
Widget build(BuildContext context) {
+ final textField = FlowyTextField(
+ hintText: LocaleKeys.document_imageBlock_embedLink_placeholder.tr(),
+ onChanged: (value) => inputText = value,
+ onEditingComplete: submit,
+ textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(
+ fontSize: 14,
+ ),
+ hintStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(
+ color: Theme.of(context).hintColor,
+ fontSize: 14,
+ ),
+ );
return Column(
children: [
const VSpace(12),
- FlowyTextField(
- hintText: LocaleKeys.document_imageBlock_embedLink_placeholder.tr(),
- onChanged: (value) => inputText = value,
- onEditingComplete: submit,
- ),
+ PlatformExtension.isDesktop
+ ? textField
+ : SizedBox(
+ height: 42,
+ child: textField,
+ ),
if (!isUrlValid) ...[
const VSpace(12),
FlowyText(
@@ -39,18 +53,23 @@ class _EmbedImageUrlWidgetState extends State {
],
const VSpace(20),
SizedBox(
- height: 32,
+ height: PlatformExtension.isMobile ? 36 : 32,
width: 300,
child: FlowyButton(
backgroundColor: Theme.of(context).colorScheme.primary,
hoverColor: Theme.of(context).colorScheme.primary.withOpacity(0.9),
showDefaultBoxDecorationOnMobile: true,
+ radius:
+ PlatformExtension.isMobile ? BorderRadius.circular(8) : null,
margin: const EdgeInsets.all(5),
text: FlowyText(
LocaleKeys.document_imageBlock_embedLink_label.tr(),
lineHeight: 1,
textAlign: TextAlign.center,
- color: Theme.of(context).colorScheme.onPrimary,
+ color: PlatformExtension.isMobile
+ ? null
+ : Theme.of(context).colorScheme.onPrimary,
+ fontSize: PlatformExtension.isMobile ? 14 : null,
),
onTap: submit,
),
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/upload_image_file_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/upload_image_file_widget.dart
index e9a6ea677d..991a7cb0b9 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/upload_image_file_widget.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/upload_image_file_widget.dart
@@ -1,5 +1,3 @@
-import 'package:flutter/material.dart';
-
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/shared/permission/permission_checker.dart';
import 'package:appflowy/startup/startup.dart';
@@ -9,6 +7,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/file_picker/file_picker_service.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
+import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
class UploadImageFileWidget extends StatelessWidget {
@@ -25,8 +24,9 @@ class UploadImageFileWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
- final child = FlowyButton(
+ Widget child = FlowyButton(
showDefaultBoxDecorationOnMobile: true,
+ radius: PlatformExtension.isMobile ? BorderRadius.circular(8.0) : null,
text: Container(
margin: const EdgeInsets.all(4.0),
alignment: Alignment.center,
@@ -38,7 +38,12 @@ class UploadImageFileWidget extends StatelessWidget {
);
if (PlatformExtension.isDesktopOrWeb) {
- return FlowyHover(child: child);
+ child = FlowyHover(child: child);
+ } else {
+ child = Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 8.0),
+ child: child,
+ );
}
return child;
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_floating_toolbar/custom_mobile_floating_toolbar.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_floating_toolbar/custom_mobile_floating_toolbar.dart
index e3b320a63d..ba170e8d24 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_floating_toolbar/custom_mobile_floating_toolbar.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_floating_toolbar/custom_mobile_floating_toolbar.dart
@@ -1,6 +1,7 @@
import 'dart:io';
import 'package:appflowy/generated/locale_keys.g.dart';
+import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
@@ -23,7 +24,7 @@ List buildMobileFloatingToolbarItems(
ContextMenuButtonItem(
label: LocaleKeys.editor_copy.tr(),
onPressed: () {
- copyCommand.execute(editorState);
+ customCopyCommand.execute(editorState);
closeToolbar();
},
),
@@ -34,7 +35,7 @@ List buildMobileFloatingToolbarItems(
ContextMenuButtonItem(
label: LocaleKeys.editor_paste.tr(),
onPressed: () {
- pasteCommand.execute(editorState);
+ customPasteCommand.execute(editorState);
closeToolbar();
},
),
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/loading.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/loading.dart
index 66ce0bef5f..1215a66dae 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/loading.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/loading.dart
@@ -8,12 +8,22 @@ class Loading {
BuildContext? loadingContext;
final BuildContext context;
+ bool hasStopped = false;
+
void start() => unawaited(
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
loadingContext = context;
+
+ if (hasStopped) {
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ Navigator.of(loadingContext!).pop();
+ loadingContext = null;
+ });
+ }
+
return const SimpleDialog(
elevation: 0.0,
backgroundColor:
@@ -33,6 +43,8 @@ class Loading {
Navigator.of(loadingContext!).pop();
loadingContext = null;
}
+
+ hasStopped = true;
}
}
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items.dart
index 8b92d51ab2..0c3868a50a 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items.dart
@@ -125,6 +125,21 @@ final numberedListSlashMenuItem = SelectionMenuItem(
},
);
+// todo list menu item
+final todoListSlashMenuItem = SelectionMenuItem(
+ getName: () => LocaleKeys.document_slashMenu_name_todoList.tr(),
+ nameBuilder: _slashMenuItemNameBuilder,
+ icon: (editorState, isSelected, style) => SelectableSvgWidget(
+ data: FlowySvgs.slash_menu_icon_checkbox_s,
+ isSelected: isSelected,
+ style: style,
+ ),
+ keywords: ['checkbox', 'todo', 'list', 'to-do', 'task'],
+ handler: (editorState, _, __) {
+ insertCheckboxAfterSelection(editorState);
+ },
+);
+
// quote menu item
final quoteSlashMenuItem = SelectionMenuItem(
getName: () => LocaleKeys.document_slashMenu_name_quote.tr(),
@@ -134,12 +149,42 @@ final quoteSlashMenuItem = SelectionMenuItem(
isSelected: isSelected,
style: style,
),
- keywords: ['quote', 'refer'],
+ keywords: ['quote', 'refer', 'blockquote', 'citation'],
handler: (editorState, _, __) {
insertQuoteAfterSelection(editorState);
},
);
+// divider menu item
+final dividerSlashMenuItem = SelectionMenuItem(
+ getName: () => LocaleKeys.document_slashMenu_name_divider.tr(),
+ nameBuilder: _slashMenuItemNameBuilder,
+ icon: (editorState, isSelected, style) => SelectableSvgWidget(
+ data: FlowySvgs.slash_menu_icon_divider_s,
+ isSelected: isSelected,
+ style: style,
+ ),
+ keywords: ['divider', 'separator', 'line', 'break', 'horizontal line'],
+ handler: (editorState, _, __) {
+ final selection = editorState.selection;
+ if (selection == null || !selection.isCollapsed) {
+ return;
+ }
+ final path = selection.end.path;
+ final node = editorState.getNodeAtPath(path);
+ final delta = node?.delta;
+ if (node == null || delta == null) {
+ return;
+ }
+ final insertedPath = delta.isEmpty ? path : path.next;
+ final transaction = editorState.transaction
+ ..insertNode(insertedPath, dividerNode())
+ ..insertNode(insertedPath, paragraphNode())
+ ..afterSelection = Selection.collapsed(Position(path: insertedPath.next));
+ editorState.apply(transaction);
+ },
+);
+
// grid & board & calendar menu item
SelectionMenuItem gridSlashMenuItem(DocumentBloc documentBloc) {
return SelectionMenuItem(
@@ -347,7 +392,7 @@ SelectionMenuItem toggleListSlashMenuItem = SelectionMenuItem.node(
isSelected: isSelected,
style: style,
),
- keywords: ['collapsed list', 'toggle list', 'list'],
+ keywords: ['collapsed list', 'toggle list', 'list', 'dropdown'],
nodeBuilder: (editorState, _) => toggleListBlockNode(),
replace: (_, node) => node.delta?.isEmpty ?? false,
);
@@ -361,7 +406,7 @@ SelectionMenuItem emojiSlashMenuItem = SelectionMenuItem(
isSelected: isSelected,
style: style,
),
- keywords: ['emoji'],
+ keywords: ['emoji', 'reaction', 'emoticon'],
handler: (editorState, menuService, context) {
final container = Overlay.of(context);
menuService.dismiss();
@@ -391,6 +436,56 @@ SelectionMenuItem aiWriterSlashMenuItem = SelectionMenuItem.node(
replace: (_, node) => false,
);
+// table menu item
+SelectionMenuItem tableSlashMenuItem = SelectionMenuItem(
+ getName: () => LocaleKeys.document_slashMenu_name_table.tr(),
+ nameBuilder: _slashMenuItemNameBuilder,
+ icon: (editorState, isSelected, style) => SelectableSvgWidget(
+ data: FlowySvgs.slash_menu_icon_simple_table_s,
+ isSelected: isSelected,
+ style: style,
+ ),
+ keywords: ['table', 'rows', 'columns', 'data'],
+ handler: (editorState, _, __) async {
+ final selection = editorState.selection;
+ if (selection == null || !selection.isCollapsed) {
+ return;
+ }
+
+ final currentNode = editorState.getNodeAtPath(selection.end.path);
+ if (currentNode == null) {
+ return;
+ }
+
+ final tableNode = TableNode.fromList([
+ ['', ''],
+ ['', ''],
+ ]);
+
+ final transaction = editorState.transaction;
+ final delta = currentNode.delta;
+ if (delta != null && delta.isEmpty) {
+ transaction
+ ..insertNode(selection.end.path, tableNode.node)
+ ..deleteNode(currentNode);
+ transaction.afterSelection = Selection.collapsed(
+ Position(
+ path: selection.end.path + [0, 0],
+ ),
+ );
+ } else {
+ transaction.insertNode(selection.end.path.next, tableNode.node);
+ transaction.afterSelection = Selection.collapsed(
+ Position(
+ path: selection.end.path.next + [0, 0],
+ ),
+ );
+ }
+
+ await editorState.apply(transaction);
+ },
+);
+
// date or reminder menu item
SelectionMenuItem dateOrReminderSlashMenuItem = SelectionMenuItem(
getName: () => LocaleKeys.document_slashMenu_name_dateOrReminder.tr(),
@@ -400,7 +495,7 @@ SelectionMenuItem dateOrReminderSlashMenuItem = SelectionMenuItem(
isSelected: isSelected,
style: style,
),
- keywords: ['insert date', 'date', 'time', 'reminder'],
+ keywords: ['insert date', 'date', 'time', 'reminder', 'schedule'],
handler: (editorState, menuService, context) =>
insertDateReference(editorState),
);
@@ -439,7 +534,7 @@ SelectionMenuItem fileSlashMenuItem = SelectionMenuItem(
isSelected: isSelected,
style: style,
),
- keywords: ['file upload', 'pdf', 'zip', 'archive', 'upload'],
+ keywords: ['file upload', 'pdf', 'zip', 'archive', 'upload', 'attachment'],
handler: (editorState, _, __) async => editorState.insertEmptyFileBlock(),
);
diff --git a/frontend/appflowy_flutter/lib/startup/deps_resolver.dart b/frontend/appflowy_flutter/lib/startup/deps_resolver.dart
index d19e0b3f7a..4136cfd07d 100644
--- a/frontend/appflowy_flutter/lib/startup/deps_resolver.dart
+++ b/frontend/appflowy_flutter/lib/startup/deps_resolver.dart
@@ -12,7 +12,6 @@ import 'package:appflowy/startup/tasks/appflowy_cloud_task.dart';
import 'package:appflowy/user/application/ai_service.dart';
import 'package:appflowy/user/application/auth/af_cloud_auth_service.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
-import 'package:appflowy/user/application/auth/supabase_auth_service.dart';
import 'package:appflowy/user/application/prelude.dart';
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
import 'package:appflowy/user/application/user_listener.dart';
@@ -124,9 +123,6 @@ void _resolveUserDeps(GetIt getIt, IntegrationMode mode) {
),
);
break;
- case AuthenticatorType.supabase:
- getIt.registerFactory(() => SupabaseAuthService());
- break;
case AuthenticatorType.appflowyCloud:
case AuthenticatorType.appflowyCloudSelfHost:
case AuthenticatorType.appflowyCloudDevelop:
diff --git a/frontend/appflowy_flutter/lib/startup/startup.dart b/frontend/appflowy_flutter/lib/startup/startup.dart
index 3dac4f229c..85be02f6a6 100644
--- a/frontend/appflowy_flutter/lib/startup/startup.dart
+++ b/frontend/appflowy_flutter/lib/startup/startup.dart
@@ -110,6 +110,7 @@ class FlowyRunner {
// this task should be first task, for handling platform errors.
// don't catch errors in test mode
if (!mode.isUnitTest) const PlatformErrorCatcherTask(),
+ if (!mode.isUnitTest) const InitSentryTask(),
// this task should be second task, for handling memory leak.
// there's a flag named _enable in memory_leak_detector.dart. If it's false, the task will be ignored.
MemoryLeakDetectorTask(),
@@ -132,7 +133,6 @@ class FlowyRunner {
// It is unable to get the device information from the test environment.
const ApplicationInfoTask(),
const HotKeyTask(),
- if (isSupabaseEnabled) InitSupabaseTask(),
if (isAppFlowyCloudEnabled) InitAppFlowyCloudTask(),
const InitAppWidgetTask(),
const InitPlatformServiceTask(),
diff --git a/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart b/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart
index d879f07578..5574749d64 100644
--- a/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart
+++ b/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart
@@ -190,9 +190,12 @@ class _ApplicationWidgetState extends State {
if (view != null) {
final view = action.arguments?[ActionArgumentKeys.view];
final rowId = action.arguments?[ActionArgumentKeys.rowId];
- AppGlobals.rootNavKey.currentContext?.pushView(view, {
- PluginArgumentKeys.rowId: rowId,
- });
+ AppGlobals.rootNavKey.currentContext?.pushView(
+ view,
+ arguments: {
+ PluginArgumentKeys.rowId: rowId,
+ },
+ );
}
}
});
diff --git a/frontend/appflowy_flutter/lib/startup/tasks/appflowy_cloud_task.dart b/frontend/appflowy_flutter/lib/startup/tasks/appflowy_cloud_task.dart
index 542e8b75a2..5aad45b3c5 100644
--- a/frontend/appflowy_flutter/lib/startup/tasks/appflowy_cloud_task.dart
+++ b/frontend/appflowy_flutter/lib/startup/tasks/appflowy_cloud_task.dart
@@ -7,7 +7,6 @@ import 'package:app_links/app_links.dart';
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/startup/tasks/app_widget.dart';
-import 'package:appflowy/startup/tasks/supabase_task.dart';
import 'package:appflowy/user/application/auth/auth_error.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/user/application/auth/device_id.dart';
@@ -22,6 +21,8 @@ import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:appflowy_result/appflowy_result.dart';
import 'package:url_protocol/url_protocol.dart';
+const appflowyDeepLinkSchema = 'appflowy-flutter';
+
class AppFlowyCloudDeepLink {
AppFlowyCloudDeepLink() {
if (_deeplinkSubscription == null) {
diff --git a/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart b/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart
index 7e10166fe4..eebb8df1cd 100644
--- a/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart
+++ b/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart
@@ -17,6 +17,7 @@ import 'package:appflowy/mobile/presentation/setting/cloud/appflowy_cloud_page.d
import 'package:appflowy/mobile/presentation/setting/font/font_picker_screen.dart';
import 'package:appflowy/mobile/presentation/setting/language/language_picker_screen.dart';
import 'package:appflowy/mobile/presentation/setting/launch_settings_page.dart';
+import 'package:appflowy/mobile/presentation/setting/workspace/invite_members_screen.dart';
import 'package:appflowy/plugins/base/color/color_picker_screen.dart';
import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/code_block/code_language_screen.dart';
@@ -97,6 +98,9 @@ GoRouter generateRouter(Widget child) {
// notifications
_mobileNotificationMultiSelectPageRoute(),
+
+ // invite members
+ _mobileInviteMembersPageRoute(),
],
// Desktop and Mobile
@@ -198,6 +202,18 @@ GoRoute _mobileNotificationMultiSelectPageRoute() {
);
}
+GoRoute _mobileInviteMembersPageRoute() {
+ return GoRoute(
+ parentNavigatorKey: AppGlobals.rootNavKey,
+ path: InviteMembersScreen.routeName,
+ pageBuilder: (context, state) {
+ return const MaterialExtendedPage(
+ child: InviteMembersScreen(),
+ );
+ },
+ );
+}
+
GoRoute _mobileCloudSettingAppFlowyCloudPageRoute() {
return GoRoute(
parentNavigatorKey: AppGlobals.rootNavKey,
@@ -477,9 +493,20 @@ GoRoute _mobileEditorScreenRoute() {
pageBuilder: (context, state) {
final id = state.uri.queryParameters[MobileDocumentScreen.viewId]!;
final title = state.uri.queryParameters[MobileDocumentScreen.viewTitle];
+ final showMoreButton = bool.tryParse(
+ state.uri.queryParameters[MobileDocumentScreen.viewShowMoreButton] ??
+ 'true',
+ );
+ final fixedTitle =
+ state.uri.queryParameters[MobileDocumentScreen.viewFixedTitle];
return MaterialExtendedPage(
- child: MobileDocumentScreen(id: id, title: title),
+ child: MobileDocumentScreen(
+ id: id,
+ title: title,
+ showMoreButton: showMoreButton ?? true,
+ fixedTitle: fixedTitle,
+ ),
);
},
);
diff --git a/frontend/appflowy_flutter/lib/startup/tasks/prelude.dart b/frontend/appflowy_flutter/lib/startup/tasks/prelude.dart
index 84c379da24..4be5f0f6f7 100644
--- a/frontend/appflowy_flutter/lib/startup/tasks/prelude.dart
+++ b/frontend/appflowy_flutter/lib/startup/tasks/prelude.dart
@@ -9,7 +9,7 @@ export 'localization.dart';
export 'memory_leak_detector.dart';
export 'platform_error_catcher.dart';
export 'platform_service.dart';
-export 'rust_sdk.dart';
-export 'supabase_task.dart';
-export 'windows.dart';
export 'recent_service_task.dart';
+export 'rust_sdk.dart';
+export 'sentry.dart';
+export 'windows.dart';
diff --git a/frontend/appflowy_flutter/lib/startup/tasks/rust_sdk.dart b/frontend/appflowy_flutter/lib/startup/tasks/rust_sdk.dart
index c02b450d79..58d6aacbc3 100644
--- a/frontend/appflowy_flutter/lib/startup/tasks/rust_sdk.dart
+++ b/frontend/appflowy_flutter/lib/startup/tasks/rust_sdk.dart
@@ -63,7 +63,6 @@ AppFlowyConfiguration _makeAppFlowyConfiguration(
device_id: deviceId,
platform: Platform.operatingSystem,
authenticator_type: env.authenticatorType.value,
- supabase_config: env.supabaseConfig,
appflowy_cloud_config: env.appflowyCloudConfig,
envs: rustEnvs,
);
diff --git a/frontend/appflowy_flutter/lib/startup/tasks/sentry.dart b/frontend/appflowy_flutter/lib/startup/tasks/sentry.dart
new file mode 100644
index 0000000000..9076569a9c
--- /dev/null
+++ b/frontend/appflowy_flutter/lib/startup/tasks/sentry.dart
@@ -0,0 +1,31 @@
+import 'package:appflowy/env/env.dart';
+import 'package:appflowy_backend/log.dart';
+import 'package:sentry_flutter/sentry_flutter.dart';
+
+import '../startup.dart';
+
+class InitSentryTask extends LaunchTask {
+ const InitSentryTask();
+
+ @override
+ Future initialize(LaunchContext context) async {
+ const dsn = Env.sentryDsn;
+ if (dsn.isEmpty) {
+ Log.info('Sentry DSN is not set, skipping initialization');
+ return;
+ }
+
+ Log.info('Initializing Sentry');
+
+ await SentryFlutter.init(
+ (options) {
+ options.dsn = dsn;
+ options.tracesSampleRate = 0.1;
+ options.profilesSampleRate = 0.1;
+ },
+ );
+ }
+
+ @override
+ Future dispose() async {}
+}
diff --git a/frontend/appflowy_flutter/lib/startup/tasks/supabase_task.dart b/frontend/appflowy_flutter/lib/startup/tasks/supabase_task.dart
deleted file mode 100644
index cb8981acdd..0000000000
--- a/frontend/appflowy_flutter/lib/startup/tasks/supabase_task.dart
+++ /dev/null
@@ -1,118 +0,0 @@
-import 'dart:async';
-import 'dart:io';
-
-import 'package:appflowy/env/cloud_env.dart';
-import 'package:appflowy/user/application/supabase_realtime.dart';
-import 'package:appflowy/workspace/application/settings/application_data_storage.dart';
-import 'package:flutter/foundation.dart';
-import 'package:hive_flutter/hive_flutter.dart';
-import 'package:path/path.dart' as p;
-import 'package:supabase_flutter/supabase_flutter.dart';
-import 'package:url_protocol/url_protocol.dart';
-
-import '../startup.dart';
-
-// ONLY supports in macOS and Windows now.
-//
-// If you need to update the schema, please update the following files:
-// - appflowy_flutter/macos/Runner/Info.plist (macOS)
-// - the callback url in Supabase dashboard
-const appflowyDeepLinkSchema = 'appflowy-flutter';
-const supabaseLoginCallback = '$appflowyDeepLinkSchema://login-callback';
-
-const hiveBoxName = 'appflowy_supabase_authentication';
-
-// Used to store the session of the supabase in case of the user switch the different folder.
-Supabase? supabase;
-SupabaseRealtimeService? realtimeService;
-
-class InitSupabaseTask extends LaunchTask {
- @override
- Future initialize(LaunchContext context) async {
- if (!isSupabaseEnabled) {
- return;
- }
-
- await supabase?.dispose();
- supabase = null;
- final initializedSupabase = await Supabase.initialize(
- url: getIt().supabaseConfig.url,
- anonKey: getIt().supabaseConfig.anon_key,
- debug: kDebugMode,
- authOptions: const FlutterAuthClientOptions(
- localStorage: SupabaseLocalStorage(),
- ),
- );
-
- if (realtimeService != null) {
- await realtimeService?.dispose();
- realtimeService = null;
- }
- realtimeService = SupabaseRealtimeService(supabase: initializedSupabase);
-
- supabase = initializedSupabase;
-
- if (Platform.isWindows) {
- // register deep link for Windows
- registerProtocolHandler(appflowyDeepLinkSchema);
- }
- }
-
- @override
- Future dispose() async {
- await realtimeService?.dispose();
- realtimeService = null;
- await supabase?.dispose();
- supabase = null;
- }
-}
-
-/// customize the supabase auth storage
-///
-/// We don't use the default one because it always save the session in the document directory.
-/// When we switch to the different folder, the session still exists.
-class SupabaseLocalStorage extends LocalStorage {
- const SupabaseLocalStorage();
-
- @override
- Future initialize() async {
- HiveCipher? encryptionCipher;
-
- // customize the path for Hive
- final path = await getIt().getPath();
- Hive.init(p.join(path, 'supabase_auth'));
- await Hive.openBox(
- hiveBoxName,
- encryptionCipher: encryptionCipher,
- );
- }
-
- @override
- Future hasAccessToken() {
- return Future.value(
- Hive.box(hiveBoxName).containsKey(
- supabasePersistSessionKey,
- ),
- );
- }
-
- @override
- Future accessToken() {
- return Future.value(
- Hive.box(hiveBoxName).get(supabasePersistSessionKey) as String?,
- );
- }
-
- @override
- Future removePersistedSession() {
- return Hive.box(hiveBoxName).delete(supabasePersistSessionKey);
- }
-
- @override
- Future persistSession(String persistSessionString) {
- return Hive.box(hiveBoxName).put(
- supabasePersistSessionKey,
- persistSessionString,
- );
- }
-}
diff --git a/frontend/appflowy_flutter/lib/user/application/auth/af_cloud_mock_auth_service.dart b/frontend/appflowy_flutter/lib/user/application/auth/af_cloud_mock_auth_service.dart
index 7c33143ff0..fac655b7fc 100644
--- a/frontend/appflowy_flutter/lib/user/application/auth/af_cloud_mock_auth_service.dart
+++ b/frontend/appflowy_flutter/lib/user/application/auth/af_cloud_mock_auth_service.dart
@@ -20,7 +20,7 @@ class AppFlowyCloudMockAuthService implements AuthService {
final String userEmail;
final BackendAuthService _appFlowyAuthService =
- BackendAuthService(AuthenticatorPB.Supabase);
+ BackendAuthService(AuthenticatorPB.AppFlowyCloud);
@override
Future> signUp({
diff --git a/frontend/appflowy_flutter/lib/user/application/auth/supabase_auth_service.dart b/frontend/appflowy_flutter/lib/user/application/auth/supabase_auth_service.dart
deleted file mode 100644
index 0dc48d7ef7..0000000000
--- a/frontend/appflowy_flutter/lib/user/application/auth/supabase_auth_service.dart
+++ /dev/null
@@ -1,252 +0,0 @@
-import 'dart:async';
-
-import 'package:appflowy/startup/tasks/prelude.dart';
-import 'package:appflowy/user/application/auth/auth_service.dart';
-import 'package:appflowy/user/application/auth/backend_auth_service.dart';
-import 'package:appflowy/user/application/auth/device_id.dart';
-import 'package:appflowy/user/application/user_service.dart';
-import 'package:appflowy_backend/dispatch/dispatch.dart';
-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:appflowy_result/appflowy_result.dart';
-import 'package:flutter/foundation.dart';
-import 'package:supabase_flutter/supabase_flutter.dart';
-
-import 'auth_error.dart';
-
-class SupabaseAuthService implements AuthService {
- SupabaseAuthService();
-
- SupabaseClient get _client => Supabase.instance.client;
- GoTrueClient get _auth => _client.auth;
-
- final BackendAuthService _backendAuthService = BackendAuthService(
- AuthenticatorPB.Supabase,
- );
-
- @override
- Future> signUp({
- required String name,
- required String email,
- required String password,
- Map params = const {},
- }) async {
- // fetch the uuid from supabase.
- final response = await _auth.signUp(
- email: email,
- password: password,
- );
- final uuid = response.user?.id;
- if (uuid == null) {
- return FlowyResult.failure(AuthError.supabaseSignUpError);
- }
- // assign the uuid to our backend service.
- // and will transfer this logic to backend later.
- return _backendAuthService.signUp(
- name: name,
- email: email,
- password: password,
- params: {
- AuthServiceMapKeys.uuid: uuid,
- },
- );
- }
-
- @override
- Future> signInWithEmailPassword({
- required String email,
- required String password,
- Map params = const {},
- }) async {
- try {
- final response = await _auth.signInWithPassword(
- email: email,
- password: password,
- );
- final uuid = response.user?.id;
- if (uuid == null) {
- return FlowyResult.failure(AuthError.supabaseSignInError);
- }
- return _backendAuthService.signInWithEmailPassword(
- email: email,
- password: password,
- params: {
- AuthServiceMapKeys.uuid: uuid,
- },
- );
- } on AuthException catch (e) {
- Log.error(e);
- return FlowyResult.failure(AuthError.supabaseSignInError);
- }
- }
-
- @override
- Future> signUpWithOAuth({
- required String platform,
- Map params = const {},
- }) async {
- // Before signing in, sign out any existing users. Otherwise, the callback will be triggered even if the user doesn't click the 'Sign In' button on the website
- if (_auth.currentUser != null) {
- await _auth.signOut();
- }
-
- final provider = platform.toProvider();
- final completer = supabaseLoginCompleter(
- onSuccess: (userId, userEmail) async {
- return _setupAuth(
- map: {
- AuthServiceMapKeys.uuid: userId,
- AuthServiceMapKeys.email: userEmail,
- AuthServiceMapKeys.deviceId: await getDeviceId(),
- },
- );
- },
- );
-
- final response = await _auth.signInWithOAuth(
- provider,
- queryParams: queryParamsForProvider(provider),
- redirectTo: supabaseLoginCallback,
- );
- if (!response) {
- completer.complete(
- FlowyResult.failure(AuthError.supabaseSignInWithOauthError),
- );
- }
- return completer.future;
- }
-
- @override
- Future signOut() async {
- await _auth.signOut();
- await _backendAuthService.signOut();
- }
-
- @override
- Future> signUpAsGuest({
- Map params = const {},
- }) async {
- // supabase don't support guest login.
- // so, just forward to our backend.
- return _backendAuthService.signUpAsGuest();
- }
-
- @override
- Future> signInWithMagicLink({
- required String email,
- Map params = const {},
- }) async {
- final completer = supabaseLoginCompleter(
- onSuccess: (userId, userEmail) async {
- return _setupAuth(
- map: {
- AuthServiceMapKeys.uuid: userId,
- AuthServiceMapKeys.email: userEmail,
- AuthServiceMapKeys.deviceId: await getDeviceId(),
- },
- );
- },
- );
-
- await _auth.signInWithOtp(
- email: email,
- emailRedirectTo: kIsWeb ? null : supabaseLoginCallback,
- );
- return completer.future;
- }
-
- @override
- Future> getUser() async {
- return UserBackendService.getCurrentUserProfile();
- }
-
- Future> getSupabaseUser() async {
- final user = _auth.currentUser;
- if (user == null) {
- return FlowyResult.failure(AuthError.supabaseGetUserError);
- }
- return FlowyResult.success(user);
- }
-
- Future> _setupAuth({
- required Map map,
- }) async {
- final payload = OauthSignInPB(
- authenticator: AuthenticatorPB.Supabase,
- map: map,
- );
-
- return UserEventOauthSignIn(payload).send().then((value) => value);
- }
-}
-
-extension on String {
- OAuthProvider toProvider() {
- switch (this) {
- case 'github':
- return OAuthProvider.github;
- case 'google':
- return OAuthProvider.google;
- case 'discord':
- return OAuthProvider.discord;
- default:
- throw UnimplementedError();
- }
- }
-}
-
-/// Creates a completer that listens to Supabase authentication state changes and
-/// completes when a user signs in.
-///
-/// This function sets up a listener on Supabase's authentication state. When a user
-/// signs in, it triggers the provided [onSuccess] callback with the user's `id` and
-/// `email`. Once the [onSuccess] callback is executed and a response is received,
-/// the completer completes with the response, and the listener is canceled.
-///
-/// Parameters:
-/// - [onSuccess]: A callback function that's executed when a user signs in. It
-/// should take in a user's `id` and `email` and return a `Future` containing either
-/// a `FlowyError` or a `UserProfilePB`.
-///
-/// Returns:
-/// A completer of type `FlowyResult`. This completer completes
-/// with the response from the [onSuccess] callback when a user signs in.
-Completer> supabaseLoginCompleter({
- required Future> Function(
- String userId,
- String userEmail,
- ) onSuccess,
-}) {
- final completer = Completer>();
- late final StreamSubscription 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) {
- final response = await onSuccess(
- user.id,
- user.email ?? user.newEmail ?? '',
- );
- // Only cancel the subscription if the Event is signedIn.
- await subscription.cancel();
- completer.complete(response);
- }
- });
- return completer;
-}
-
-Map queryParamsForProvider(OAuthProvider provider) {
- switch (provider) {
- case OAuthProvider.google:
- return {
- 'access_type': 'offline',
- 'prompt': 'consent',
- };
- case OAuthProvider.github:
- case OAuthProvider.discord:
- default:
- return {};
- }
-}
diff --git a/frontend/appflowy_flutter/lib/user/application/auth/supabase_mock_auth_service.dart b/frontend/appflowy_flutter/lib/user/application/auth/supabase_mock_auth_service.dart
deleted file mode 100644
index bd2620caaa..0000000000
--- a/frontend/appflowy_flutter/lib/user/application/auth/supabase_mock_auth_service.dart
+++ /dev/null
@@ -1,113 +0,0 @@
-import 'dart:async';
-
-import 'package:appflowy/user/application/auth/auth_service.dart';
-import 'package:appflowy/user/application/auth/backend_auth_service.dart';
-import 'package:appflowy/user/application/user_service.dart';
-import 'package:appflowy_backend/dispatch/dispatch.dart';
-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:appflowy_result/appflowy_result.dart';
-import 'package:supabase_flutter/supabase_flutter.dart';
-
-import 'auth_error.dart';
-
-/// Only used for testing.
-class SupabaseMockAuthService implements AuthService {
- SupabaseMockAuthService();
- static OauthSignInPB? signInPayload;
-
- SupabaseClient get _client => Supabase.instance.client;
- GoTrueClient get _auth => _client.auth;
-
- final BackendAuthService _appFlowyAuthService =
- BackendAuthService(AuthenticatorPB.Supabase);
-
- @override
- Future> signUp({
- required String name,
- required String email,
- required String password,
- Map params = const {},
- }) async {
- throw UnimplementedError();
- }
-
- @override
- Future> signInWithEmailPassword({
- required String email,
- required String password,
- Map params = const {},
- }) async {
- throw UnimplementedError();
- }
-
- @override
- Future> signUpWithOAuth({
- required String platform,
- Map params = const {},
- }) async {
- const password = "AppFlowyTest123!";
- const email = "supabase_integration_test@appflowy.io";
- try {
- if (_auth.currentSession == null) {
- try {
- await _auth.signInWithPassword(
- password: password,
- email: email,
- );
- } catch (e) {
- Log.error(e);
- return FlowyResult.failure(AuthError.supabaseSignUpError);
- }
- }
- // Check if the user is already logged in.
- final session = _auth.currentSession!;
- final uuid = session.user.id;
-
- // Create the OAuth sign-in payload.
- final payload = OauthSignInPB(
- authenticator: AuthenticatorPB.Supabase,
- map: {
- AuthServiceMapKeys.uuid: uuid,
- AuthServiceMapKeys.email: email,
- AuthServiceMapKeys.deviceId: 'MockDeviceId',
- },
- );
-
- // Send the sign-in event and handle the response.
- return UserEventOauthSignIn(payload).send().then((value) => value);
- } on AuthException catch (e) {
- Log.error(e);
- return FlowyResult.failure(AuthError.supabaseSignInError);
- }
- }
-
- @override
- Future signOut() async {
- // await _auth.signOut();
- await _appFlowyAuthService.signOut();
- }
-
- @override
- Future> signUpAsGuest({
- Map params = const {},
- }) async {
- // supabase don't support guest login.
- // so, just forward to our backend.
- return _appFlowyAuthService.signUpAsGuest();
- }
-
- @override
- Future> signInWithMagicLink({
- required String email,
- Map params = const {},
- }) async {
- throw UnimplementedError();
- }
-
- @override
- Future> getUser() async {
- return UserBackendService.getCurrentUserProfile();
- }
-}
diff --git a/frontend/appflowy_flutter/lib/workspace/application/recent/cached_recent_service.dart b/frontend/appflowy_flutter/lib/workspace/application/recent/cached_recent_service.dart
index 7361ab6da2..a5381ce17f 100644
--- a/frontend/appflowy_flutter/lib/workspace/application/recent/cached_recent_service.dart
+++ b/frontend/appflowy_flutter/lib/workspace/application/recent/cached_recent_service.dart
@@ -33,7 +33,7 @@ class CachedRecentService {
final _listener = RecentViewsListener();
Future> recentViews() async {
- if (_isInitialized) return _recentViews;
+ if (_isInitialized || _completer.isCompleted) return _recentViews;
_isInitialized = true;
@@ -76,7 +76,10 @@ class CachedRecentService {
(recentViews) {
return FlowyResult.success(
RepeatedRecentViewPB(
- items: recentViews.items.where((e) => !e.item.isSpace),
+ // filter the space view and the orphan view
+ items: recentViews.items.where(
+ (e) => !e.item.isSpace && e.item.id != e.item.parentViewId,
+ ),
),
);
},
diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/ai/local_ai_on_boarding_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/ai/local_ai_on_boarding_bloc.dart
index 3a56e93a32..902cb948b8 100644
--- a/frontend/appflowy_flutter/lib/workspace/application/settings/ai/local_ai_on_boarding_bloc.dart
+++ b/frontend/appflowy_flutter/lib/workspace/application/settings/ai/local_ai_on_boarding_bloc.dart
@@ -15,8 +15,11 @@ part 'local_ai_on_boarding_bloc.freezed.dart';
class LocalAIOnBoardingBloc
extends Bloc {
- LocalAIOnBoardingBloc(this.userProfile)
- : super(const LocalAIOnBoardingState()) {
+ LocalAIOnBoardingBloc(
+ this.userProfile,
+ this.member,
+ this.workspaceId,
+ ) : super(const LocalAIOnBoardingState()) {
_userService = UserBackendService(userId: userProfile.id);
_successListenable = getIt();
_successListenable.addListener(_onPaymentSuccessful);
@@ -36,6 +39,8 @@ class LocalAIOnBoardingBloc
}
final UserProfilePB userProfile;
+ final WorkspaceMemberPB member;
+ final String workspaceId;
late final IUserBackendService _userService;
late final SubscriptionSuccessListenable _successListenable;
@@ -48,7 +53,7 @@ class LocalAIOnBoardingBloc
addSubscription: (plan) async {
emit(state.copyWith(isLoading: true));
final result = await _userService.createSubscription(
- userProfile.workspaceId,
+ workspaceId,
plan,
);
@@ -72,7 +77,7 @@ class LocalAIOnBoardingBloc
);
},
(err) {
- Log.error("Failed to get subscription plans: $err");
+ Log.warn("Failed to get subscription plans: $err");
},
);
},
@@ -86,7 +91,7 @@ class LocalAIOnBoardingBloc
}
void _loadSubscriptionPlans() {
- final payload = UserWorkspaceIdPB()..workspaceId = userProfile.workspaceId;
+ final payload = UserWorkspaceIdPB()..workspaceId = workspaceId;
UserEventGetWorkspaceSubscriptionInfo(payload).send().then((result) {
if (!isClosed) {
add(LocalAIOnBoardingEvent.didGetSubscriptionPlans(result));
diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/ai/settings_ai_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/ai/settings_ai_bloc.dart
index fd07db4d48..af0b390ebd 100644
--- a/frontend/appflowy_flutter/lib/workspace/application/settings/ai/settings_ai_bloc.dart
+++ b/frontend/appflowy_flutter/lib/workspace/application/settings/ai/settings_ai_bloc.dart
@@ -11,8 +11,11 @@ import 'package:freezed_annotation/freezed_annotation.dart';
part 'settings_ai_bloc.freezed.dart';
class SettingsAIBloc extends Bloc {
- SettingsAIBloc(this.userProfile, WorkspaceMemberPB? member)
- : _userListener = UserListener(userProfile: userProfile),
+ SettingsAIBloc(
+ this.userProfile,
+ this.workspaceId,
+ WorkspaceMemberPB? member,
+ ) : _userListener = UserListener(userProfile: userProfile),
_userService = UserBackendService(userId: userProfile.id),
super(SettingsAIState(userProfile: userProfile, member: member)) {
_dispatch();
@@ -36,6 +39,7 @@ class SettingsAIBloc extends Bloc {
final UserListener _userListener;
final UserProfilePB userProfile;
final UserBackendService _userService;
+ final String workspaceId;
@override
Future close() async {
@@ -92,7 +96,7 @@ class SettingsAIBloc extends Bloc {
AIModelPB? model,
}) {
final payload = UpdateUserWorkspaceSettingPB(
- workspaceId: userProfile.workspaceId,
+ workspaceId: workspaceId,
);
if (disableSearchIndexing != null) {
payload.disableSearchIndexing = disableSearchIndexing;
@@ -112,7 +116,7 @@ class SettingsAIBloc extends Bloc {
);
void _loadUserWorkspaceSetting() {
- final payload = UserWorkspaceIdPB(workspaceId: userProfile.workspaceId);
+ final payload = UserWorkspaceIdPB(workspaceId: workspaceId);
UserEventGetWorkspaceSetting(payload).send().then((result) {
result.fold((settings) {
if (!isClosed) {
@@ -133,7 +137,8 @@ class SettingsAIEvent with _$SettingsAIEvent {
) = _DidLoadWorkspaceSetting;
const factory SettingsAIEvent.toggleAISearch() = _toggleAISearch;
- const factory SettingsAIEvent.refreshMember(WorkspaceMemberPB member) = _RefreshMember;
+ const factory SettingsAIEvent.refreshMember(WorkspaceMemberPB member) =
+ _RefreshMember;
const factory SettingsAIEvent.selectModel(AIModelPB model) = _SelectAIModel;
diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/settings_dialog_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/settings_dialog_bloc.dart
index 36f2603dda..f28900d18a 100644
--- a/frontend/appflowy_flutter/lib/workspace/application/settings/settings_dialog_bloc.dart
+++ b/frontend/appflowy_flutter/lib/workspace/application/settings/settings_dialog_bloc.dart
@@ -90,7 +90,6 @@ class SettingsDialogBloc
]) async {
if ([
AuthenticatorPB.Local,
- AuthenticatorPB.Supabase,
].contains(userProfile.authenticator)) {
return false;
}
diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/supabase_cloud_setting_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/supabase_cloud_setting_bloc.dart
deleted file mode 100644
index 9308a06a98..0000000000
--- a/frontend/appflowy_flutter/lib/workspace/application/settings/supabase_cloud_setting_bloc.dart
+++ /dev/null
@@ -1,103 +0,0 @@
-import 'package:appflowy/env/backend_env.dart';
-import 'package:appflowy/env/cloud_env.dart';
-import 'package:appflowy/plugins/database/application/defines.dart';
-import 'package:appflowy/startup/startup.dart';
-import 'package:appflowy_backend/dispatch/dispatch.dart';
-import 'package:appflowy_backend/log.dart';
-import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
-import 'package:appflowy_result/appflowy_result.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:freezed_annotation/freezed_annotation.dart';
-
-import 'cloud_setting_listener.dart';
-
-part 'supabase_cloud_setting_bloc.freezed.dart';
-
-class SupabaseCloudSettingBloc
- extends Bloc {
- SupabaseCloudSettingBloc({
- required CloudSettingPB setting,
- }) : _listener = UserCloudConfigListener(),
- super(SupabaseCloudSettingState.initial(setting)) {
- _dispatch();
- }
-
- final UserCloudConfigListener _listener;
-
- @override
- Future close() async {
- await _listener.stop();
- return super.close();
- }
-
- void _dispatch() {
- on(
- (event, emit) async {
- await event.when(
- initial: () async {
- _listener.start(
- onSettingChanged: (result) {
- if (isClosed) {
- return;
- }
- result.fold(
- (setting) =>
- add(SupabaseCloudSettingEvent.didReceiveSetting(setting)),
- (error) => Log.error(error),
- );
- },
- );
- },
- enableSync: (bool enable) async {
- final update = UpdateCloudConfigPB.create()..enableSync = enable;
- await updateCloudConfig(update);
- },
- didReceiveSetting: (CloudSettingPB setting) {
- emit(
- state.copyWith(
- setting: setting,
- loadingState: LoadingState.finish(FlowyResult.success(null)),
- ),
- );
- },
- enableEncrypt: (bool enable) {
- final update = UpdateCloudConfigPB.create()..enableEncrypt = enable;
- updateCloudConfig(update);
- emit(state.copyWith(loadingState: const LoadingState.loading()));
- },
- );
- },
- );
- }
-
- Future updateCloudConfig(UpdateCloudConfigPB setting) async {
- await UserEventSetCloudConfig(setting).send();
- }
-}
-
-@freezed
-class SupabaseCloudSettingEvent with _$SupabaseCloudSettingEvent {
- const factory SupabaseCloudSettingEvent.initial() = _Initial;
- const factory SupabaseCloudSettingEvent.didReceiveSetting(
- CloudSettingPB setting,
- ) = _DidSyncSupabaseConfig;
- const factory SupabaseCloudSettingEvent.enableSync(bool enable) = _EnableSync;
- const factory SupabaseCloudSettingEvent.enableEncrypt(bool enable) =
- _EnableEncrypt;
-}
-
-@freezed
-class SupabaseCloudSettingState with _$SupabaseCloudSettingState {
- const factory SupabaseCloudSettingState({
- required LoadingState loadingState,
- required SupabaseConfiguration config,
- required CloudSettingPB setting,
- }) = _SupabaseCloudSettingState;
-
- factory SupabaseCloudSettingState.initial(CloudSettingPB setting) =>
- SupabaseCloudSettingState(
- loadingState: LoadingState.finish(FlowyResult.success(null)),
- setting: setting,
- config: getIt().supabaseConfig,
- );
-}
diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/supabase_cloud_urls_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/supabase_cloud_urls_bloc.dart
deleted file mode 100644
index fdd4cbef21..0000000000
--- a/frontend/appflowy_flutter/lib/workspace/application/settings/supabase_cloud_urls_bloc.dart
+++ /dev/null
@@ -1,128 +0,0 @@
-import 'package:appflowy/env/backend_env.dart';
-import 'package:appflowy/env/cloud_env.dart';
-import 'package:appflowy/generated/locale_keys.g.dart';
-import 'package:appflowy/startup/startup.dart';
-import 'package:appflowy_backend/dispatch/dispatch.dart';
-import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
-import 'package:easy_localization/easy_localization.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:freezed_annotation/freezed_annotation.dart';
-
-import 'appflowy_cloud_setting_bloc.dart';
-
-part 'supabase_cloud_urls_bloc.freezed.dart';
-
-class SupabaseCloudURLsBloc
- extends Bloc {
- SupabaseCloudURLsBloc() : super(SupabaseCloudURLsState.initial()) {
- on((event, emit) async {
- await event.when(
- updateUrl: (String url) {
- emit(
- state.copyWith(
- updatedUrl: url,
- showRestartHint: url.isNotEmpty && state.upatedAnonKey.isNotEmpty,
- urlError: null,
- ),
- );
- },
- updateAnonKey: (String anonKey) {
- emit(
- state.copyWith(
- upatedAnonKey: anonKey,
- showRestartHint:
- anonKey.isNotEmpty && state.updatedUrl.isNotEmpty,
- anonKeyError: null,
- ),
- );
- },
- confirmUpdate: () async {
- if (state.updatedUrl.isEmpty) {
- emit(
- state.copyWith(
- urlError:
- LocaleKeys.settings_menu_cloudSupabaseUrlCanNotBeEmpty.tr(),
- anonKeyError: null,
- restartApp: false,
- ),
- );
- return;
- }
-
- if (state.upatedAnonKey.isEmpty) {
- emit(
- state.copyWith(
- urlError: null,
- anonKeyError: LocaleKeys
- .settings_menu_cloudSupabaseAnonKeyCanNotBeEmpty
- .tr(),
- restartApp: false,
- ),
- );
- return;
- }
-
- validateUrl(state.updatedUrl).fold(
- (_) async {
- await useSupabaseCloud(
- url: state.updatedUrl,
- anonKey: state.upatedAnonKey,
- );
-
- add(const SupabaseCloudURLsEvent.didSaveConfig());
- },
- (error) => emit(state.copyWith(urlError: error)),
- );
- },
- didSaveConfig: () {
- emit(
- state.copyWith(
- urlError: null,
- anonKeyError: null,
- restartApp: true,
- ),
- );
- },
- );
- });
- }
-
- Future updateCloudConfig(UpdateCloudConfigPB setting) async {
- await UserEventSetCloudConfig(setting).send();
- }
-}
-
-@freezed
-class SupabaseCloudURLsEvent with _$SupabaseCloudURLsEvent {
- const factory SupabaseCloudURLsEvent.updateUrl(String text) = _UpdateUrl;
- const factory SupabaseCloudURLsEvent.updateAnonKey(String text) =
- _UpdateAnonKey;
- const factory SupabaseCloudURLsEvent.confirmUpdate() = _UpdateConfig;
- const factory SupabaseCloudURLsEvent.didSaveConfig() = _DidSaveConfig;
-}
-
-@freezed
-class SupabaseCloudURLsState with _$SupabaseCloudURLsState {
- const factory SupabaseCloudURLsState({
- required SupabaseConfiguration config,
- required String updatedUrl,
- required String upatedAnonKey,
- required String? urlError,
- required String? anonKeyError,
- required bool restartApp,
- required bool showRestartHint,
- }) = _SupabaseCloudURLsState;
-
- factory SupabaseCloudURLsState.initial() {
- final config = getIt().supabaseConfig;
- return SupabaseCloudURLsState(
- updatedUrl: config.url,
- upatedAnonKey: config.anon_key,
- urlError: null,
- anonKeyError: null,
- restartApp: false,
- showRestartHint: config.url.isNotEmpty && config.anon_key.isNotEmpty,
- config: config,
- );
- }
-}
diff --git a/frontend/appflowy_flutter/lib/workspace/application/sidebar/space/space_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/sidebar/space/space_bloc.dart
index fb96d53d0a..feef136206 100644
--- a/frontend/appflowy_flutter/lib/workspace/application/sidebar/space/space_bloc.dart
+++ b/frontend/appflowy_flutter/lib/workspace/application/sidebar/space/space_bloc.dart
@@ -68,6 +68,8 @@ class SpaceBloc extends Bloc {
(event, emit) async {
await event.when(
initial: (userProfile, workspaceId, openFirstPage) async {
+ this.openFirstPage = openFirstPage;
+
_initial(userProfile, workspaceId);
final (spaces, publicViews, privateViews) = await _getSpaces();
@@ -305,7 +307,7 @@ class SpaceBloc extends Bloc {
SpaceEvent.initial(
userProfile,
workspaceId,
- openFirstPage: true,
+ openFirstPage: openFirstPage,
),
);
},
@@ -353,6 +355,7 @@ class SpaceBloc extends Bloc {
String? _workspaceId;
late UserProfilePB userProfile;
WorkspaceSectionsListener? _listener;
+ bool openFirstPage = false;
@override
Future close() async {
diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/desktop_home_screen.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/desktop_home_screen.dart
index b2f9c89889..a8d768aa79 100644
--- a/frontend/appflowy_flutter/lib/workspace/presentation/home/desktop_home_screen.dart
+++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/desktop_home_screen.dart
@@ -26,6 +26,7 @@ import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
import 'package:flowy_infra_ui/style_widget/container.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:sentry/sentry.dart';
import 'package:sized_context/sized_context.dart';
import 'package:styled_widget/styled_widget.dart';
@@ -55,6 +56,7 @@ class DesktopHomeScreen extends StatelessWidget {
(workspaceSettingPB) => workspaceSettingPB as WorkspaceSettingPB,
(error) => null,
);
+
final userProfile = snapshots.data?[1].fold(
(userProfilePB) => userProfilePB as UserProfilePB,
(error) => null,
@@ -66,6 +68,14 @@ class DesktopHomeScreen extends StatelessWidget {
return const WorkspaceFailedScreen();
}
+ Sentry.configureScope(
+ (scope) => scope.setUser(
+ SentryUser(
+ id: userProfile.id.toString(),
+ ),
+ ),
+ );
+
return AFFocusManager(
child: MultiBlocProvider(
key: ValueKey(userProfile.id),
diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/fix_data_widget.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/fix_data_widget.dart
index 6f556ec5b6..2cecb25b30 100644
--- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/fix_data_widget.dart
+++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/fix_data_widget.dart
@@ -118,6 +118,8 @@ class WorkspaceDataManager {
final List unlistedChildViews = [];
// Views whose parent is not in allViews
final List orphanViews = [];
+ // Row pages
+ final List rowPageViews = [];
try {
if (workspace == null || allViews == null) {
@@ -145,6 +147,11 @@ class WorkspaceDataManager {
continue;
}
+ if (parentView.id == view.id) {
+ rowPageViews.add(view);
+ continue;
+ }
+
final childViewsOfParent =
await ViewBackendService.getChildViews(viewId: parentView.id)
.getOrThrow();
@@ -165,7 +172,11 @@ class WorkspaceDataManager {
}
for (final view in orphanViews) {
- Log.debug('[workspace] orphanViews: ${view.toProto3Json()}');
+ Log.info('[workspace] orphanViews: ${view.toProto3Json()}');
+ }
+
+ for (final view in rowPageViews) {
+ Log.info('[workspace] rowPageViews: ${view.toProto3Json()}');
}
if (!dryRun && unlistedChildViews.isNotEmpty) {
diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/settings_ai_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/settings_ai_view.dart
index c5e160fb03..0c3965c731 100644
--- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/settings_ai_view.dart
+++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/settings_ai_view.dart
@@ -40,15 +40,17 @@ class SettingsAIView extends StatelessWidget {
super.key,
required this.userProfile,
required this.member,
+ required this.workspaceId,
});
final UserProfilePB userProfile;
final WorkspaceMemberPB? member;
+ final String workspaceId;
@override
Widget build(BuildContext context) {
return BlocProvider(
- create: (_) => SettingsAIBloc(userProfile, member)
+ create: (_) => SettingsAIBloc(userProfile, workspaceId, member)
..add(const SettingsAIEvent.started()),
child: BlocBuilder(
builder: (context, state) {
@@ -63,6 +65,7 @@ class SettingsAIView extends StatelessWidget {
_LocalAIOnBoarding(
userProfile: userProfile,
member: state.member!,
+ workspaceId: workspaceId,
),
);
}
@@ -127,9 +130,11 @@ class _LocalAIOnBoarding extends StatelessWidget {
const _LocalAIOnBoarding({
required this.userProfile,
required this.member,
+ required this.workspaceId,
});
final UserProfilePB userProfile;
final WorkspaceMemberPB member;
+ final String workspaceId;
@override
Widget build(BuildContext context) {
@@ -137,8 +142,9 @@ class _LocalAIOnBoarding extends StatelessWidget {
return BillingGateGuard(
builder: (context) {
return BlocProvider(
- create: (context) => LocalAIOnBoardingBloc(userProfile)
- ..add(const LocalAIOnBoardingEvent.started()),
+ create: (context) =>
+ LocalAIOnBoardingBloc(userProfile, member, workspaceId)
+ ..add(const LocalAIOnBoardingEvent.started()),
child: BlocBuilder(
builder: (context, state) {
// Show the local AI settings if the user has purchased the AI Local plan
diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/settings_dialog.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/settings_dialog.dart
index 5a3905ed21..c633aa3577 100644
--- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/settings_dialog.dart
+++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/settings_dialog.dart
@@ -120,7 +120,11 @@ class SettingsDialog extends StatelessWidget {
return const SettingsShortcutsView();
case SettingsPage.ai:
if (user.authenticator == AuthenticatorPB.AppFlowyCloud) {
- return SettingsAIView(userProfile: user, member: member);
+ return SettingsAIView(
+ userProfile: user,
+ member: member,
+ workspaceId: workspaceId,
+ );
} else {
return const AIFeatureOnlySupportedWhenUsingAppFlowyCloud();
}
diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_cloud.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_cloud.dart
index 7191e2cc9d..1b8248d376 100644
--- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_cloud.dart
+++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_cloud.dart
@@ -22,7 +22,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'setting_appflowy_cloud.dart';
-import 'setting_supabase_cloud.dart';
class SettingCloud extends StatelessWidget {
const SettingCloud({required this.restartAppFlowy, super.key});
@@ -80,8 +79,6 @@ class SettingCloud extends StatelessWidget {
switch (cloudType) {
case AuthenticatorType.local:
return SettingLocalCloud(restartAppFlowy: restartAppFlowy);
- case AuthenticatorType.supabase:
- return SettingSupabaseCloudView(restartAppFlowy: restartAppFlowy);
case AuthenticatorType.appflowyCloud:
return AppFlowyCloudViewSetting(restartAppFlowy: restartAppFlowy);
case AuthenticatorType.appflowyCloudSelfHost:
@@ -112,9 +109,6 @@ class CloudTypeSwitcher extends StatelessWidget {
// Only show the appflowyCloudDevelop in develop mode
final values = AuthenticatorType.values.where((element) {
// Supabase will going to be removed in the future
- if (element == AuthenticatorType.supabase) {
- return false;
- }
return isDevelopMode || element != AuthenticatorType.appflowyCloudDevelop;
}).toList();
@@ -218,8 +212,6 @@ String titleFromCloudType(AuthenticatorType cloudType) {
switch (cloudType) {
case AuthenticatorType.local:
return LocaleKeys.settings_menu_cloudLocal.tr();
- case AuthenticatorType.supabase:
- return LocaleKeys.settings_menu_cloudSupabase.tr();
case AuthenticatorType.appflowyCloud:
return LocaleKeys.settings_menu_cloudAppFlowy.tr();
case AuthenticatorType.appflowyCloudSelfHost:
diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_supabase_cloud.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_supabase_cloud.dart
deleted file mode 100644
index 6751213251..0000000000
--- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_supabase_cloud.dart
+++ /dev/null
@@ -1,339 +0,0 @@
-import 'package:flutter/gestures.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-
-import 'package:appflowy/core/helpers/url_launcher.dart';
-import 'package:appflowy/generated/locale_keys.g.dart';
-import 'package:appflowy/workspace/application/settings/supabase_cloud_setting_bloc.dart';
-import 'package:appflowy/workspace/application/settings/supabase_cloud_urls_bloc.dart';
-import 'package:appflowy/workspace/presentation/home/toast.dart';
-import 'package:appflowy/workspace/presentation/settings/widgets/_restart_app_button.dart';
-import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
-import 'package:appflowy_backend/dispatch/dispatch.dart';
-import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
-import 'package:appflowy_backend/protobuf/flowy-user/user_setting.pb.dart';
-import 'package:appflowy_result/appflowy_result.dart';
-import 'package:easy_localization/easy_localization.dart';
-import 'package:flowy_infra/size.dart';
-import 'package:flowy_infra/theme_extension.dart';
-import 'package:flowy_infra_ui/flowy_infra_ui.dart';
-import 'package:flowy_infra_ui/widget/error_page.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-
-class SettingSupabaseCloudView extends StatelessWidget {
- const SettingSupabaseCloudView({required this.restartAppFlowy, super.key});
-
- final VoidCallback restartAppFlowy;
-
- @override
- Widget build(BuildContext context) {
- return FutureBuilder>(
- future: UserEventGetCloudConfig().send(),
- builder: (context, snapshot) {
- if (snapshot.data != null &&
- snapshot.connectionState == ConnectionState.done) {
- return snapshot.data!.fold(
- (setting) {
- return BlocProvider(
- create: (context) => SupabaseCloudSettingBloc(
- setting: setting,
- )..add(const SupabaseCloudSettingEvent.initial()),
- child: Column(
- children: [
- BlocBuilder(
- builder: (context, state) {
- return const Column(
- children: [
- SupabaseEnableSync(),
- EnableEncrypt(),
- ],
- );
- },
- ),
- const VSpace(40),
- const SupabaseSelfhostTip(),
- SupabaseCloudURLs(
- didUpdateUrls: restartAppFlowy,
- ),
- ],
- ),
- );
- },
- (err) {
- return FlowyErrorPage.message(err.toString(), howToFix: "");
- },
- );
- } else {
- return const Center(
- child: CircularProgressIndicator(),
- );
- }
- },
- );
- }
-}
-
-class SupabaseCloudURLs extends StatelessWidget {
- const SupabaseCloudURLs({super.key, required this.didUpdateUrls});
-
- final VoidCallback didUpdateUrls;
-
- @override
- Widget build(BuildContext context) {
- return BlocProvider(
- create: (context) => SupabaseCloudURLsBloc(),
- child: BlocListener(
- listener: (context, state) async {
- if (state.restartApp) {
- didUpdateUrls();
- }
- },
- child: BlocBuilder(
- builder: (context, state) {
- return Column(
- children: [
- SupabaseInput(
- title: LocaleKeys.settings_menu_cloudSupabaseUrl.tr(),
- url: state.config.url,
- hint: LocaleKeys.settings_menu_cloudURLHint.tr(),
- onChanged: (text) {
- context
- .read()
- .add(SupabaseCloudURLsEvent.updateUrl(text));
- },
- error: state.urlError,
- ),
- SupabaseInput(
- title: LocaleKeys.settings_menu_cloudSupabaseAnonKey.tr(),
- url: state.config.anon_key,
- hint: LocaleKeys.settings_menu_cloudURLHint.tr(),
- onChanged: (text) {
- context
- .read()
- .add(SupabaseCloudURLsEvent.updateAnonKey(text));
- },
- error: state.anonKeyError,
- ),
- const VSpace(20),
- RestartButton(
- onClick: () => _restartApp(context),
- showRestartHint: state.showRestartHint,
- ),
- ],
- );
- },
- ),
- ),
- );
- }
-
- void _restartApp(BuildContext context) {
- NavigatorAlertDialog(
- title: LocaleKeys.settings_menu_restartAppTip.tr(),
- confirm: () => context
- .read()
- .add(const SupabaseCloudURLsEvent.confirmUpdate()),
- ).show(context);
- }
-}
-
-class EnableEncrypt extends StatelessWidget {
- const EnableEncrypt({super.key});
-
- @override
- Widget build(BuildContext context) {
- return BlocBuilder(
- builder: (context, state) {
- final indicator = state.loadingState.when(
- loading: () => const CircularProgressIndicator.adaptive(),
- finish: (successOrFail) => const SizedBox.shrink(),
- idle: () => const SizedBox.shrink(),
- );
-
- return Column(
- children: [
- Row(
- children: [
- FlowyText.medium(LocaleKeys.settings_menu_enableEncrypt.tr()),
- const Spacer(),
- indicator,
- const HSpace(3),
- Switch.adaptive(
- activeColor: Theme.of(context).colorScheme.primary,
- onChanged: state.setting.enableEncrypt
- ? null
- : (bool value) {
- context.read().add(
- SupabaseCloudSettingEvent.enableEncrypt(value),
- );
- },
- value: state.setting.enableEncrypt,
- ),
- ],
- ),
- Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- IntrinsicHeight(
- child: Opacity(
- opacity: 0.6,
- child: FlowyText.medium(
- LocaleKeys.settings_menu_enableEncryptPrompt.tr(),
- maxLines: 13,
- ),
- ),
- ),
- const VSpace(6),
- SizedBox(
- height: 40,
- child: FlowyTooltip(
- message: LocaleKeys.settings_menu_clickToCopySecret.tr(),
- child: FlowyButton(
- disable: !state.setting.enableEncrypt,
- decoration: BoxDecoration(
- borderRadius: Corners.s5Border,
- border: Border.all(
- color: Theme.of(context).colorScheme.secondary,
- ),
- ),
- text: FlowyText.medium(state.setting.encryptSecret),
- onTap: () async {
- await Clipboard.setData(
- ClipboardData(text: state.setting.encryptSecret),
- );
- showMessageToast(LocaleKeys.message_copy_success.tr());
- },
- ),
- ),
- ),
- ],
- ),
- ],
- );
- },
- );
- }
-}
-
-class SupabaseEnableSync extends StatelessWidget {
- const SupabaseEnableSync({super.key});
-
- @override
- Widget build(BuildContext context) {
- return BlocBuilder(
- builder: (context, state) {
- return Row(
- children: [
- FlowyText.medium(LocaleKeys.settings_menu_enableSync.tr()),
- const Spacer(),
- Switch.adaptive(
- activeColor: Theme.of(context).colorScheme.primary,
- onChanged: (bool value) {
- context.read().add(
- SupabaseCloudSettingEvent.enableSync(value),
- );
- },
- value: state.setting.enableSync,
- ),
- ],
- );
- },
- );
- }
-}
-
-@visibleForTesting
-class SupabaseInput extends StatefulWidget {
- const SupabaseInput({
- super.key,
- required this.title,
- required this.url,
- required this.hint,
- required this.error,
- required this.onChanged,
- });
-
- final String title;
- final String url;
- final String hint;
- final String? error;
- final Function(String) onChanged;
-
- @override
- SupabaseInputState createState() => SupabaseInputState();
-}
-
-class SupabaseInputState extends State {
- late final _controller = TextEditingController(text: widget.url);
-
- @override
- void dispose() {
- _controller.dispose();
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- return TextField(
- controller: _controller,
- style: const TextStyle(fontSize: 12.0),
- decoration: InputDecoration(
- contentPadding: const EdgeInsets.symmetric(vertical: 6),
- labelText: widget.title,
- labelStyle: Theme.of(context)
- .textTheme
- .titleMedium!
- .copyWith(fontWeight: FontWeight.w400, fontSize: 16),
- enabledBorder: UnderlineInputBorder(
- borderSide:
- BorderSide(color: AFThemeExtension.of(context).onBackground),
- ),
- focusedBorder: UnderlineInputBorder(
- borderSide: BorderSide(color: Theme.of(context).colorScheme.primary),
- ),
- hintText: widget.hint,
- errorText: widget.error,
- ),
- onChanged: widget.onChanged,
- );
- }
-}
-
-class SupabaseSelfhostTip extends StatelessWidget {
- const SupabaseSelfhostTip({super.key});
-
- final url =
- "https://docs.appflowy.io/docs/guides/appflowy/self-hosting-appflowy-using-supabase";
-
- @override
- Widget build(BuildContext context) {
- return Opacity(
- opacity: 0.6,
- child: RichText(
- text: TextSpan(
- children: [
- TextSpan(
- text: LocaleKeys.settings_menu_selfHostStart.tr(),
- style: Theme.of(context).textTheme.bodySmall!,
- ),
- TextSpan(
- text: " ${LocaleKeys.settings_menu_selfHostContent.tr()} ",
- style: Theme.of(context).textTheme.bodyMedium!.copyWith(
- fontSize: FontSizes.s14,
- color: Theme.of(context).colorScheme.primary,
- decoration: TextDecoration.underline,
- ),
- recognizer: TapGestureRecognizer()
- ..onTap = () => afLaunchUrlString(url),
- ),
- TextSpan(
- text: LocaleKeys.settings_menu_selfHostEnd.tr(),
- style: Theme.of(context).textTheme.bodySmall!,
- ),
- ],
- ),
- ),
- );
- }
-}
diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialogs.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialogs.dart
index d57f2a5442..80beca1200 100644
--- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialogs.dart
+++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialogs.dart
@@ -1,5 +1,3 @@
-import 'package:flutter/material.dart';
-
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/startup/tasks/app_widget.dart';
@@ -13,6 +11,7 @@ import 'package:flowy_infra_ui/widget/buttons/primary_button.dart';
import 'package:flowy_infra_ui/widget/buttons/secondary_button.dart';
import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
+import 'package:flutter/material.dart';
import 'package:toastification/toastification.dart';
export 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
@@ -354,34 +353,37 @@ class _MToast extends StatelessWidget {
@override
Widget build(BuildContext context) {
- // only support success type
- assert(type == ToastificationType.success);
-
+ final hintText = FlowyText.regular(
+ message,
+ fontSize: 16.0,
+ figmaLineHeight: 18.0,
+ color: Colors.white,
+ maxLines: 10,
+ );
return Container(
alignment: Alignment.bottomCenter,
- padding: const EdgeInsets.only(bottom: 100),
+ padding: const EdgeInsets.only(bottom: 100, left: 16, right: 16),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 13.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.0),
color: const Color(0xE5171717),
),
- child: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- const FlowySvg(
- FlowySvgs.success_s,
- blendMode: null,
- ),
- const HSpace(8.0),
- FlowyText.regular(
- message,
- fontSize: 16.0,
- figmaLineHeight: 18.0,
- color: Colors.white,
- ),
- ],
- ),
+ child: type == ToastificationType.success
+ ? Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ if (type == ToastificationType.success) ...[
+ const FlowySvg(
+ FlowySvgs.success_s,
+ blendMode: null,
+ ),
+ const HSpace(8.0),
+ ],
+ hintText,
+ ],
+ )
+ : hintText,
),
);
}
diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/view_title_bar.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/view_title_bar.dart
index 43b14d9d1a..68849661b5 100644
--- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/view_title_bar.dart
+++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/view_title_bar.dart
@@ -79,11 +79,11 @@ class ViewTitleBar extends StatelessWidget {
final child = FlowyTooltip(
key: ValueKey(view.id),
message: view.name,
- child: _ViewTitle(
+ child: ViewTitle(
view: view,
behavior: i == views.length - 1
- ? _ViewTitleBehavior.editable // only the last one is editable
- : _ViewTitleBehavior.uneditable, // others are not editable
+ ? ViewTitleBehavior.editable // only the last one is editable
+ : ViewTitleBehavior.uneditable, // others are not editable
onUpdated: () {
context
.read()
@@ -103,27 +103,28 @@ class ViewTitleBar extends StatelessWidget {
}
}
-enum _ViewTitleBehavior {
+enum ViewTitleBehavior {
editable,
uneditable,
}
-class _ViewTitle extends StatefulWidget {
- const _ViewTitle({
+class ViewTitle extends StatefulWidget {
+ const ViewTitle({
+ super.key,
required this.view,
- this.behavior = _ViewTitleBehavior.editable,
+ this.behavior = ViewTitleBehavior.editable,
required this.onUpdated,
});
final ViewPB view;
- final _ViewTitleBehavior behavior;
+ final ViewTitleBehavior behavior;
final VoidCallback onUpdated;
@override
- State<_ViewTitle> createState() => _ViewTitleState();
+ State createState() => _ViewTitleState();
}
-class _ViewTitleState extends State<_ViewTitle> {
+class _ViewTitleState extends State {
final popoverController = PopoverController();
final textEditingController = TextEditingController();
@@ -137,7 +138,7 @@ class _ViewTitleState extends State<_ViewTitle> {
@override
Widget build(BuildContext context) {
- final isEditable = widget.behavior == _ViewTitleBehavior.editable;
+ final isEditable = widget.behavior == ViewTitleBehavior.editable;
return BlocProvider(
create: (_) =>
diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart
index af5370dbfa..be69e14375 100644
--- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart
+++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart
@@ -263,10 +263,12 @@ class FlowyButton extends StatelessWidget {
(Platform.isIOS || Platform.isAndroid)
? BoxDecoration(
border: Border.all(
- color: borderColor ??
- Theme.of(context).colorScheme.surfaceContainerHighest,
- width: 1.0,
- ))
+ color: borderColor ??
+ Theme.of(context).colorScheme.outline,
+ width: 1.0,
+ ),
+ borderRadius: radius,
+ )
: null);
return Container(
diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/divider.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/divider.dart
index d53362dbdb..7f4b630386 100644
--- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/divider.dart
+++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/divider.dart
@@ -4,14 +4,20 @@ import 'package:flutter/material.dart';
class FlowyDivider extends StatelessWidget {
const FlowyDivider({
super.key,
+ this.padding,
});
+ final EdgeInsets? padding;
+
@override
Widget build(BuildContext context) {
- return Divider(
- height: 1.0,
- thickness: 1.0,
- color: AFThemeExtension.of(context).borderColor,
+ return Padding(
+ padding: padding ?? EdgeInsets.zero,
+ child: Divider(
+ height: 1.0,
+ thickness: 1.0,
+ color: AFThemeExtension.of(context).borderColor,
+ ),
);
}
}
diff --git a/frontend/appflowy_flutter/pubspec.lock b/frontend/appflowy_flutter/pubspec.lock
index 5d16830e41..267fb80c21 100644
--- a/frontend/appflowy_flutter/pubspec.lock
+++ b/frontend/appflowy_flutter/pubspec.lock
@@ -53,11 +53,11 @@ packages:
dependency: "direct main"
description:
path: "."
- ref: "9d3e854"
- resolved-ref: "9d3e854f11fd9d732535ce5f5b1c8f41517479a1"
+ ref: "8e17d14"
+ resolved-ref: "8e17d1447eea0b57ff92e31dbe88796ce759fb37"
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
source: git
- version: "3.1.0"
+ version: "3.2.0"
appflowy_editor_plugins:
dependency: "direct main"
description:
@@ -742,10 +742,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
- sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7
+ sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
url: "https://pub.dev"
source: hosted
- version: "3.0.1"
+ version: "4.0.0"
flutter_localizations:
dependency: transitive
description: flutter
@@ -808,7 +808,7 @@ packages:
source: hosted
version: "0.6.5"
flutter_svg:
- dependency: "direct main"
+ dependency: transitive
description:
name: flutter_svg
sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c
@@ -1083,14 +1083,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.19.0"
- intl_utils:
- dependency: transitive
- description:
- name: intl_utils
- sha256: c2b1f5c72c25512cbeef5ab015c008fc50fe7e04813ba5541c25272300484bf4
- url: "https://pub.dev"
- source: hosted
- version: "2.8.7"
io:
dependency: transitive
description:
@@ -1116,7 +1108,7 @@ packages:
source: hosted
version: "0.7.0"
isolates:
- dependency: "direct main"
+ dependency: transitive
description:
name: isolates
sha256: ce89e4141b27b877326d3715be2dceac7a7ba89f3229785816d2d318a75ddf28
@@ -1215,10 +1207,10 @@ packages:
dependency: transitive
description:
name: lints
- sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
+ sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
url: "https://pub.dev"
source: hosted
- version: "3.0.0"
+ version: "4.0.0"
loading_indicator:
dependency: transitive
description:
@@ -1236,7 +1228,7 @@ packages:
source: hosted
version: "0.1.5"
logger:
- dependency: "direct main"
+ dependency: transitive
description:
name: logger
sha256: "697d067c60c20999686a0add96cf6aba723b3aa1f83ecf806a8097231529ec32"
@@ -1475,14 +1467,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.11.1"
- pdf_widget_wrapper:
- dependency: transitive
- description:
- name: pdf_widget_wrapper
- sha256: c930860d987213a3d58c7ec3b7ecf8085c3897f773e8dc23da9cae60a5d6d0f5
- url: "https://pub.dev"
- source: hosted
- version: "1.0.4"
percent_indicator:
dependency: "direct main"
description:
@@ -1603,14 +1587,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.0"
- printing:
- dependency: transitive
- description:
- name: printing
- sha256: cc4b256a5a89d5345488e3318897b595867f5181b8c5ed6fc63bfa5f2044aec3
- url: "https://pub.dev"
- source: hosted
- version: "5.13.1"
process:
dependency: transitive
description:
@@ -1747,6 +1723,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.3.8"
+ sentry:
+ dependency: "direct main"
+ description:
+ name: sentry
+ sha256: "0f787e27ff617e4f88f7074977240406a9c5509444bac64a4dfa5b3200fb5632"
+ url: "https://pub.dev"
+ source: hosted
+ version: "8.7.0"
+ sentry_flutter:
+ dependency: "direct main"
+ description:
+ name: sentry_flutter
+ sha256: fbbb47d72ccca48be25bf3c2ced6ab6e872991af3a0ba78e54be8d138f2e053f
+ url: "https://pub.dev"
+ source: hosted
+ version: "8.7.0"
share_plus:
dependency: "direct main"
description:
diff --git a/frontend/appflowy_flutter/pubspec.yaml b/frontend/appflowy_flutter/pubspec.yaml
index b8caf2c183..d6a7f61930 100644
--- a/frontend/appflowy_flutter/pubspec.yaml
+++ b/frontend/appflowy_flutter/pubspec.yaml
@@ -77,7 +77,6 @@ dependencies:
linked_scroll_controller: ^0.2.0
hotkey_manager: ^0.1.7
fixnum: ^1.1.0
- flutter_svg: ^2.0.7
protobuf: ^3.1.0
collection: ^1.17.1
bloc: ^8.1.2
@@ -133,10 +132,8 @@ dependencies:
auto_size_text_field: ^2.2.3
reorderable_tabbar: ^1.0.6
shimmer: ^3.0.0
- isolates: ^3.0.3+8
markdown_widget: ^2.3.2+6
markdown:
- logger: ^2.4.0
# Desktop Drop uses Cross File (XFile) data type
desktop_drop: ^0.4.4
@@ -154,9 +151,11 @@ dependencies:
scroll_to_index: ^3.0.1
extended_text_field: ^15.0.0
extended_text_library: ^12.0.0
+ sentry_flutter: ^8.7.0
+ sentry: ^8.7.0
dev_dependencies:
- flutter_lints: ^3.0.1
+ flutter_lints: ^4.0.0
flutter_test:
sdk: flutter
@@ -191,7 +190,7 @@ dependency_overrides:
appflowy_editor:
git:
url: https://github.com/AppFlowy-IO/appflowy-editor.git
- ref: "9d3e854"
+ ref: "8e17d14"
appflowy_editor_plugins:
git:
diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.lock b/frontend/appflowy_tauri/src-tauri/Cargo.lock
index 64cc436a15..99f7541e17 100644
--- a/frontend/appflowy_tauri/src-tauri/Cargo.lock
+++ b/frontend/appflowy_tauri/src-tauri/Cargo.lock
@@ -172,7 +172,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
[[package]]
name = "app-error"
version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d503905#d5039059313804103f34eee49ee9844c255a99c0"
dependencies = [
"anyhow",
"bincode",
@@ -192,7 +192,7 @@ dependencies = [
[[package]]
name = "appflowy-ai-client"
version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d503905#d5039059313804103f34eee49ee9844c255a99c0"
dependencies = [
"anyhow",
"bytes",
@@ -826,11 +826,12 @@ dependencies = [
[[package]]
name = "client-api"
version = "0.2.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d503905#d5039059313804103f34eee49ee9844c255a99c0"
dependencies = [
"again",
"anyhow",
"app-error",
+ "arc-swap",
"async-trait",
"bincode",
"brotli",
@@ -876,7 +877,7 @@ dependencies = [
[[package]]
name = "client-api-entity"
version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d503905#d5039059313804103f34eee49ee9844c255a99c0"
dependencies = [
"collab-entity",
"collab-rt-entity",
@@ -888,7 +889,7 @@ dependencies = [
[[package]]
name = "client-websocket"
version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d503905#d5039059313804103f34eee49ee9844c255a99c0"
dependencies = [
"futures-channel",
"futures-util",
@@ -962,15 +963,16 @@ dependencies = [
[[package]]
name = "collab"
version = "0.2.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6a5e7e49c159fcf782df84208cdb26c212c28ede#6a5e7e49c159fcf782df84208cdb26c212c28ede"
dependencies = [
"anyhow",
+ "arc-swap",
"async-trait",
"bincode",
"bytes",
"chrono",
"js-sys",
- "parking_lot 0.12.1",
+ "lazy_static",
"serde",
"serde_json",
"serde_repr",
@@ -986,7 +988,7 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.2.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6a5e7e49c159fcf782df84208cdb26c212c28ede#6a5e7e49c159fcf782df84208cdb26c212c28ede"
dependencies = [
"anyhow",
"async-trait",
@@ -995,11 +997,11 @@ dependencies = [
"collab-entity",
"collab-plugins",
"dashmap 5.5.3",
+ "futures",
"getrandom 0.2.10",
"js-sys",
"lazy_static",
"nanoid",
- "parking_lot 0.12.1",
"rayon",
"serde",
"serde_json",
@@ -1016,14 +1018,14 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.2.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6a5e7e49c159fcf782df84208cdb26c212c28ede#6a5e7e49c159fcf782df84208cdb26c212c28ede"
dependencies = [
"anyhow",
+ "arc-swap",
"collab",
"collab-entity",
"getrandom 0.2.10",
"nanoid",
- "parking_lot 0.12.1",
"serde",
"serde_json",
"thiserror",
@@ -1036,7 +1038,7 @@ dependencies = [
[[package]]
name = "collab-entity"
version = "0.2.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6a5e7e49c159fcf782df84208cdb26c212c28ede#6a5e7e49c159fcf782df84208cdb26c212c28ede"
dependencies = [
"anyhow",
"bytes",
@@ -1055,14 +1057,15 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.2.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6a5e7e49c159fcf782df84208cdb26c212c28ede#6a5e7e49c159fcf782df84208cdb26c212c28ede"
dependencies = [
"anyhow",
+ "arc-swap",
"chrono",
"collab",
"collab-entity",
+ "dashmap 5.5.3",
"getrandom 0.2.10",
- "parking_lot 0.12.1",
"serde",
"serde_json",
"serde_repr",
@@ -1077,13 +1080,17 @@ name = "collab-integrate"
version = "0.1.0"
dependencies = [
"anyhow",
+ "arc-swap",
"async-trait",
"collab",
+ "collab-database",
+ "collab-document",
"collab-entity",
+ "collab-folder",
"collab-plugins",
+ "collab-user",
"futures",
"lib-infra",
- "parking_lot 0.12.1",
"serde",
"serde_json",
"tokio",
@@ -1093,7 +1100,7 @@ dependencies = [
[[package]]
name = "collab-plugins"
version = "0.2.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6a5e7e49c159fcf782df84208cdb26c212c28ede#6a5e7e49c159fcf782df84208cdb26c212c28ede"
dependencies = [
"anyhow",
"async-stream",
@@ -1109,7 +1116,6 @@ dependencies = [
"indexed_db_futures",
"js-sys",
"lazy_static",
- "parking_lot 0.12.1",
"rand 0.8.5",
"rocksdb",
"serde",
@@ -1132,7 +1138,7 @@ dependencies = [
[[package]]
name = "collab-rt-entity"
version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d503905#d5039059313804103f34eee49ee9844c255a99c0"
dependencies = [
"anyhow",
"bincode",
@@ -1157,7 +1163,7 @@ dependencies = [
[[package]]
name = "collab-rt-protocol"
version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d503905#d5039059313804103f34eee49ee9844c255a99c0"
dependencies = [
"anyhow",
"async-trait",
@@ -1174,13 +1180,12 @@ dependencies = [
[[package]]
name = "collab-user"
version = "0.2.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6a5e7e49c159fcf782df84208cdb26c212c28ede#6a5e7e49c159fcf782df84208cdb26c212c28ede"
dependencies = [
"anyhow",
"collab",
"collab-entity",
"getrandom 0.2.10",
- "parking_lot 0.12.1",
"serde",
"serde_json",
"tokio",
@@ -1546,7 +1551,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
[[package]]
name = "database-entity"
version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d503905#d5039059313804103f34eee49ee9844c255a99c0"
dependencies = [
"anyhow",
"app-error",
@@ -1972,6 +1977,7 @@ dependencies = [
"anyhow",
"appflowy-local-ai",
"appflowy-plugin",
+ "arc-swap",
"base64 0.21.5",
"bytes",
"dashmap 6.0.1",
@@ -1989,7 +1995,6 @@ dependencies = [
"log",
"md5",
"notify",
- "parking_lot 0.12.1",
"pin-project",
"protobuf",
"reqwest",
@@ -2072,6 +2077,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"appflowy-local-ai",
+ "arc-swap",
"base64 0.21.5",
"bytes",
"client-api",
@@ -2079,6 +2085,7 @@ dependencies = [
"collab-entity",
"collab-integrate",
"collab-plugins",
+ "dashmap 6.0.1",
"diesel",
"flowy-ai",
"flowy-ai-pub",
@@ -2105,7 +2112,6 @@ dependencies = [
"lib-dispatch",
"lib-infra",
"lib-log",
- "parking_lot 0.12.1",
"semver",
"serde",
"serde_json",
@@ -2135,6 +2141,7 @@ name = "flowy-database2"
version = "0.1.0"
dependencies = [
"anyhow",
+ "arc-swap",
"async-stream",
"async-trait",
"bytes",
@@ -2159,7 +2166,6 @@ dependencies = [
"lib-dispatch",
"lib-infra",
"nanoid",
- "parking_lot 0.12.1",
"protobuf",
"rayon",
"rust_decimal",
@@ -2170,6 +2176,7 @@ dependencies = [
"strum",
"strum_macros 0.25.2",
"tokio",
+ "tokio-util",
"tracing",
"url",
"validator",
@@ -2231,7 +2238,6 @@ dependencies = [
"lib-dispatch",
"lib-infra",
"nanoid",
- "parking_lot 0.12.1",
"protobuf",
"scraper 0.18.1",
"serde",
@@ -2302,6 +2308,7 @@ dependencies = [
name = "flowy-folder"
version = "0.1.0"
dependencies = [
+ "arc-swap",
"async-trait",
"bytes",
"chrono",
@@ -2323,7 +2330,6 @@ dependencies = [
"lib-dispatch",
"lib-infra",
"nanoid",
- "parking_lot 0.12.1",
"protobuf",
"regex",
"serde",
@@ -2418,14 +2424,17 @@ name = "flowy-server"
version = "0.1.0"
dependencies = [
"anyhow",
+ "arc-swap",
"bytes",
"chrono",
"client-api",
"collab",
+ "collab-database",
"collab-document",
"collab-entity",
"collab-folder",
"collab-plugins",
+ "dashmap 6.0.1",
"flowy-ai-pub",
"flowy-database-pub",
"flowy-document-pub",
@@ -2445,7 +2454,6 @@ dependencies = [
"lib-dispatch",
"lib-infra",
"mime_guess",
- "parking_lot 0.12.1",
"postgrest",
"rand 0.8.5",
"reqwest",
@@ -2481,7 +2489,6 @@ dependencies = [
"diesel_derives",
"diesel_migrations",
"libsqlite3-sys",
- "parking_lot 0.12.1",
"r2d2",
"scheduled-thread-pool",
"serde",
@@ -2539,6 +2546,7 @@ name = "flowy-user"
version = "0.1.0"
dependencies = [
"anyhow",
+ "arc-swap",
"base64 0.21.5",
"bytes",
"chrono",
@@ -2551,6 +2559,7 @@ dependencies = [
"collab-integrate",
"collab-plugins",
"collab-user",
+ "dashmap 6.0.1",
"diesel",
"diesel_derives",
"fancy-regex 0.11.0",
@@ -2567,7 +2576,6 @@ dependencies = [
"lib-dispatch",
"lib-infra",
"once_cell",
- "parking_lot 0.12.1",
"protobuf",
"semver",
"serde",
@@ -3068,7 +3076,7 @@ dependencies = [
[[package]]
name = "gotrue"
version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d503905#d5039059313804103f34eee49ee9844c255a99c0"
dependencies = [
"anyhow",
"futures-util",
@@ -3085,7 +3093,7 @@ dependencies = [
[[package]]
name = "gotrue-entity"
version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d503905#d5039059313804103f34eee49ee9844c255a99c0"
dependencies = [
"anyhow",
"app-error",
@@ -3517,7 +3525,7 @@ dependencies = [
[[package]]
name = "infra"
version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d503905#d5039059313804103f34eee49ee9844c255a99c0"
dependencies = [
"anyhow",
"bytes",
@@ -3782,7 +3790,6 @@ dependencies = [
"futures-util",
"getrandom 0.2.10",
"nanoid",
- "parking_lot 0.12.1",
"pin-project",
"protobuf",
"serde",
@@ -6115,7 +6122,7 @@ dependencies = [
[[package]]
name = "shared-entity"
version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d503905#d5039059313804103f34eee49ee9844c255a99c0"
dependencies = [
"anyhow",
"app-error",
diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.toml b/frontend/appflowy_tauri/src-tauri/Cargo.toml
index 25aab8120f..23e30f4bfd 100644
--- a/frontend/appflowy_tauri/src-tauri/Cargo.toml
+++ b/frontend/appflowy_tauri/src-tauri/Cargo.toml
@@ -116,13 +116,13 @@ custom-protocol = ["tauri/custom-protocol"]
# To switch to the local path, run:
# scripts/tool/update_collab_source.sh
# ⚠️⚠️⚠️️
-collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" }
-collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" }
-collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" }
-collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" }
-collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" }
-collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" }
-collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" }
+collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6a5e7e49c159fcf782df84208cdb26c212c28ede" }
+collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6a5e7e49c159fcf782df84208cdb26c212c28ede" }
+collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6a5e7e49c159fcf782df84208cdb26c212c28ede" }
+collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6a5e7e49c159fcf782df84208cdb26c212c28ede" }
+collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6a5e7e49c159fcf782df84208cdb26c212c28ede" }
+collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6a5e7e49c159fcf782df84208cdb26c212c28ede" }
+collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6a5e7e49c159fcf782df84208cdb26c212c28ede" }
# Working directory: frontend
# To update the commit ID, run:
diff --git a/frontend/appflowy_tauri/src-tauri/src/init.rs b/frontend/appflowy_tauri/src-tauri/src/init.rs
index 636735e5f4..4903e1fe34 100644
--- a/frontend/appflowy_tauri/src-tauri/src/init.rs
+++ b/frontend/appflowy_tauri/src-tauri/src/init.rs
@@ -1,9 +1,8 @@
-use flowy_core::config::AppFlowyCoreConfig;
-use flowy_core::{AppFlowyCore, MutexAppFlowyCore, DEFAULT_NAME};
-use lib_dispatch::runtime::AFPluginRuntime;
-use std::rc::Rc;
-
use dotenv::dotenv;
+use flowy_core::config::AppFlowyCoreConfig;
+use flowy_core::{AppFlowyCore, DEFAULT_NAME};
+use lib_dispatch::runtime::AFPluginRuntime;
+use std::sync::Mutex;
pub fn read_env() {
dotenv().ok();
@@ -25,7 +24,7 @@ pub fn read_env() {
}
}
-pub fn init_flowy_core() -> MutexAppFlowyCore {
+pub(crate) fn init_appflowy_core() -> MutexAppFlowyCore {
let config_json = include_str!("../tauri.conf.json");
let config: tauri_utils::config::Config = serde_json::from_str(config_json).unwrap();
@@ -61,9 +60,19 @@ pub fn init_flowy_core() -> MutexAppFlowyCore {
)
.log_filter("trace", vec!["appflowy_tauri".to_string()]);
- let runtime = Rc::new(AFPluginRuntime::new().unwrap());
+ let runtime = Arc::new(AFPluginRuntime::new().unwrap());
let cloned_runtime = runtime.clone();
runtime.block_on(async move {
MutexAppFlowyCore::new(AppFlowyCore::new(config, cloned_runtime, None).await)
})
}
+
+pub struct MutexAppFlowyCore(pub Arc>);
+
+impl MutexAppFlowyCore {
+ fn new(appflowy_core: AppFlowyCore) -> Self {
+ Self(Arc::new(Mutex::new(appflowy_core)))
+ }
+}
+unsafe impl Sync for MutexAppFlowyCore {}
+unsafe impl Send for MutexAppFlowyCore {}
diff --git a/frontend/appflowy_tauri/src-tauri/src/main.rs b/frontend/appflowy_tauri/src-tauri/src/main.rs
index 6a69de07fd..5f12d1be81 100644
--- a/frontend/appflowy_tauri/src-tauri/src/main.rs
+++ b/frontend/appflowy_tauri/src-tauri/src/main.rs
@@ -11,17 +11,18 @@ mod init;
mod notification;
mod request;
+use crate::init::init_appflowy_core;
+use crate::request::invoke_request;
use flowy_notification::{register_notification_sender, unregister_all_notification_sender};
-use init::*;
use notification::*;
-use request::*;
use tauri::Manager;
+
extern crate dotenv;
fn main() {
tauri_plugin_deep_link::prepare(DEEP_LINK_SCHEME);
- let flowy_core = init_flowy_core();
+ let flowy_core = init_appflowy_core();
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![invoke_request])
.manage(flowy_core)
diff --git a/frontend/appflowy_tauri/src-tauri/src/request.rs b/frontend/appflowy_tauri/src-tauri/src/request.rs
index 146d303cc0..ff69a438c9 100644
--- a/frontend/appflowy_tauri/src-tauri/src/request.rs
+++ b/frontend/appflowy_tauri/src-tauri/src/request.rs
@@ -1,4 +1,4 @@
-use flowy_core::;
+use crate::init::MutexAppFlowyCore;
use lib_dispatch::prelude::{
AFPluginDispatcher, AFPluginEventResponse, AFPluginRequest, StatusCode,
};
@@ -39,7 +39,7 @@ pub async fn invoke_request(
) -> AFTauriResponse {
let request: AFPluginRequest = request.into();
let state: State = app_handler.state();
- let dispatcher = state.0.lock().dispatcher();
+ let dispatcher = state.0.lock().unwrap().dispatcher();
let response = AFPluginDispatcher::sync_send(dispatcher, request);
response.into()
}
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/image_upload/UploadImage.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/image_upload/UploadImage.tsx
index a6b66a4c1f..d39da68caf 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/image_upload/UploadImage.tsx
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/image_upload/UploadImage.tsx
@@ -11,15 +11,13 @@ export function UploadImage({ onDone }: { onDone?: (url: string) => void }) {
const checkTauriFile = useCallback(
async (url: string) => {
- const { readBinaryFile } = await import('@tauri-apps/api/fs');
-
- const buffer = await readBinaryFile(url);
- const blob = new Blob([buffer]);
-
- if (blob.size > MAX_IMAGE_SIZE) {
- notify.error(t('document.imageBlock.error.invalidImageSize'));
- return false;
- }
+ // const { readBinaryFile } = await import('@tauri-apps/api/fs');
+ // const buffer = await readBinaryFile(url);
+ // const blob = new Blob([buffer]);
+ // if (blob.size > MAX_IMAGE_SIZE) {
+ // notify.error(t('document.imageBlock.error.invalidImageSize'));
+ // return false;
+ // }
return true;
},
diff --git a/frontend/appflowy_web_app/src-tauri/Cargo.lock b/frontend/appflowy_web_app/src-tauri/Cargo.lock
index 7af7287706..56ffd76575 100644
--- a/frontend/appflowy_web_app/src-tauri/Cargo.lock
+++ b/frontend/appflowy_web_app/src-tauri/Cargo.lock
@@ -163,7 +163,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
[[package]]
name = "app-error"
version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d503905#d5039059313804103f34eee49ee9844c255a99c0"
dependencies = [
"anyhow",
"bincode",
@@ -183,7 +183,7 @@ dependencies = [
[[package]]
name = "appflowy-ai-client"
version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d503905#d5039059313804103f34eee49ee9844c255a99c0"
dependencies = [
"anyhow",
"bytes",
@@ -800,11 +800,12 @@ dependencies = [
[[package]]
name = "client-api"
version = "0.2.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d503905#d5039059313804103f34eee49ee9844c255a99c0"
dependencies = [
"again",
"anyhow",
"app-error",
+ "arc-swap",
"async-trait",
"bincode",
"brotli",
@@ -850,7 +851,7 @@ dependencies = [
[[package]]
name = "client-api-entity"
version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d503905#d5039059313804103f34eee49ee9844c255a99c0"
dependencies = [
"collab-entity",
"collab-rt-entity",
@@ -862,7 +863,7 @@ dependencies = [
[[package]]
name = "client-websocket"
version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d503905#d5039059313804103f34eee49ee9844c255a99c0"
dependencies = [
"futures-channel",
"futures-util",
@@ -945,15 +946,16 @@ dependencies = [
[[package]]
name = "collab"
version = "0.2.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6a5e7e49c159fcf782df84208cdb26c212c28ede#6a5e7e49c159fcf782df84208cdb26c212c28ede"
dependencies = [
"anyhow",
+ "arc-swap",
"async-trait",
"bincode",
"bytes",
"chrono",
"js-sys",
- "parking_lot 0.12.1",
+ "lazy_static",
"serde",
"serde_json",
"serde_repr",
@@ -969,7 +971,7 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.2.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6a5e7e49c159fcf782df84208cdb26c212c28ede#6a5e7e49c159fcf782df84208cdb26c212c28ede"
dependencies = [
"anyhow",
"async-trait",
@@ -978,11 +980,11 @@ dependencies = [
"collab-entity",
"collab-plugins",
"dashmap 5.5.3",
+ "futures",
"getrandom 0.2.12",
"js-sys",
"lazy_static",
"nanoid",
- "parking_lot 0.12.1",
"rayon",
"serde",
"serde_json",
@@ -999,14 +1001,14 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.2.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6a5e7e49c159fcf782df84208cdb26c212c28ede#6a5e7e49c159fcf782df84208cdb26c212c28ede"
dependencies = [
"anyhow",
+ "arc-swap",
"collab",
"collab-entity",
"getrandom 0.2.12",
"nanoid",
- "parking_lot 0.12.1",
"serde",
"serde_json",
"thiserror",
@@ -1019,7 +1021,7 @@ dependencies = [
[[package]]
name = "collab-entity"
version = "0.2.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6a5e7e49c159fcf782df84208cdb26c212c28ede#6a5e7e49c159fcf782df84208cdb26c212c28ede"
dependencies = [
"anyhow",
"bytes",
@@ -1038,14 +1040,15 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.2.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6a5e7e49c159fcf782df84208cdb26c212c28ede#6a5e7e49c159fcf782df84208cdb26c212c28ede"
dependencies = [
"anyhow",
+ "arc-swap",
"chrono",
"collab",
"collab-entity",
+ "dashmap 5.5.3",
"getrandom 0.2.12",
- "parking_lot 0.12.1",
"serde",
"serde_json",
"serde_repr",
@@ -1060,13 +1063,17 @@ name = "collab-integrate"
version = "0.1.0"
dependencies = [
"anyhow",
+ "arc-swap",
"async-trait",
"collab",
+ "collab-database",
+ "collab-document",
"collab-entity",
+ "collab-folder",
"collab-plugins",
+ "collab-user",
"futures",
"lib-infra",
- "parking_lot 0.12.1",
"serde",
"serde_json",
"tokio",
@@ -1076,7 +1083,7 @@ dependencies = [
[[package]]
name = "collab-plugins"
version = "0.2.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6a5e7e49c159fcf782df84208cdb26c212c28ede#6a5e7e49c159fcf782df84208cdb26c212c28ede"
dependencies = [
"anyhow",
"async-stream",
@@ -1092,7 +1099,6 @@ dependencies = [
"indexed_db_futures",
"js-sys",
"lazy_static",
- "parking_lot 0.12.1",
"rand 0.8.5",
"rocksdb",
"serde",
@@ -1115,7 +1121,7 @@ dependencies = [
[[package]]
name = "collab-rt-entity"
version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d503905#d5039059313804103f34eee49ee9844c255a99c0"
dependencies = [
"anyhow",
"bincode",
@@ -1140,7 +1146,7 @@ dependencies = [
[[package]]
name = "collab-rt-protocol"
version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d503905#d5039059313804103f34eee49ee9844c255a99c0"
dependencies = [
"anyhow",
"async-trait",
@@ -1157,13 +1163,12 @@ dependencies = [
[[package]]
name = "collab-user"
version = "0.2.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6a5e7e49c159fcf782df84208cdb26c212c28ede#6a5e7e49c159fcf782df84208cdb26c212c28ede"
dependencies = [
"anyhow",
"collab",
"collab-entity",
"getrandom 0.2.12",
- "parking_lot 0.12.1",
"serde",
"serde_json",
"tokio",
@@ -1536,7 +1541,7 @@ checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
[[package]]
name = "database-entity"
version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d503905#d5039059313804103f34eee49ee9844c255a99c0"
dependencies = [
"anyhow",
"app-error",
@@ -2002,6 +2007,7 @@ dependencies = [
"anyhow",
"appflowy-local-ai",
"appflowy-plugin",
+ "arc-swap",
"base64 0.21.7",
"bytes",
"dashmap 6.0.1",
@@ -2019,7 +2025,6 @@ dependencies = [
"log",
"md5",
"notify",
- "parking_lot 0.12.1",
"pin-project",
"protobuf",
"reqwest",
@@ -2102,6 +2107,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"appflowy-local-ai",
+ "arc-swap",
"base64 0.21.7",
"bytes",
"client-api",
@@ -2109,6 +2115,7 @@ dependencies = [
"collab-entity",
"collab-integrate",
"collab-plugins",
+ "dashmap 6.0.1",
"diesel",
"flowy-ai",
"flowy-ai-pub",
@@ -2135,7 +2142,6 @@ dependencies = [
"lib-dispatch",
"lib-infra",
"lib-log",
- "parking_lot 0.12.1",
"semver",
"serde",
"serde_json",
@@ -2165,6 +2171,7 @@ name = "flowy-database2"
version = "0.1.0"
dependencies = [
"anyhow",
+ "arc-swap",
"async-stream",
"async-trait",
"bytes",
@@ -2189,7 +2196,6 @@ dependencies = [
"lib-dispatch",
"lib-infra",
"nanoid",
- "parking_lot 0.12.1",
"protobuf",
"rayon",
"rust_decimal",
@@ -2200,6 +2206,7 @@ dependencies = [
"strum",
"strum_macros 0.25.3",
"tokio",
+ "tokio-util",
"tracing",
"url",
"validator",
@@ -2261,7 +2268,6 @@ dependencies = [
"lib-dispatch",
"lib-infra",
"nanoid",
- "parking_lot 0.12.1",
"protobuf",
"scraper 0.18.1",
"serde",
@@ -2332,6 +2338,7 @@ dependencies = [
name = "flowy-folder"
version = "0.1.0"
dependencies = [
+ "arc-swap",
"async-trait",
"bytes",
"chrono",
@@ -2353,7 +2360,6 @@ dependencies = [
"lib-dispatch",
"lib-infra",
"nanoid",
- "parking_lot 0.12.1",
"protobuf",
"regex",
"serde",
@@ -2448,14 +2454,17 @@ name = "flowy-server"
version = "0.1.0"
dependencies = [
"anyhow",
+ "arc-swap",
"bytes",
"chrono",
"client-api",
"collab",
+ "collab-database",
"collab-document",
"collab-entity",
"collab-folder",
"collab-plugins",
+ "dashmap 6.0.1",
"flowy-ai-pub",
"flowy-database-pub",
"flowy-document-pub",
@@ -2475,7 +2484,6 @@ dependencies = [
"lib-dispatch",
"lib-infra",
"mime_guess",
- "parking_lot 0.12.1",
"postgrest",
"rand 0.8.5",
"reqwest",
@@ -2511,7 +2519,6 @@ dependencies = [
"diesel_derives",
"diesel_migrations",
"libsqlite3-sys",
- "parking_lot 0.12.1",
"r2d2",
"scheduled-thread-pool",
"serde",
@@ -2569,6 +2576,7 @@ name = "flowy-user"
version = "0.1.0"
dependencies = [
"anyhow",
+ "arc-swap",
"base64 0.21.7",
"bytes",
"chrono",
@@ -2581,6 +2589,7 @@ dependencies = [
"collab-integrate",
"collab-plugins",
"collab-user",
+ "dashmap 6.0.1",
"diesel",
"diesel_derives",
"fancy-regex 0.11.0",
@@ -2597,7 +2606,6 @@ dependencies = [
"lib-dispatch",
"lib-infra",
"once_cell",
- "parking_lot 0.12.1",
"protobuf",
"semver",
"serde",
@@ -3135,7 +3143,7 @@ dependencies = [
[[package]]
name = "gotrue"
version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d503905#d5039059313804103f34eee49ee9844c255a99c0"
dependencies = [
"anyhow",
"futures-util",
@@ -3152,7 +3160,7 @@ dependencies = [
[[package]]
name = "gotrue-entity"
version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d503905#d5039059313804103f34eee49ee9844c255a99c0"
dependencies = [
"anyhow",
"app-error",
@@ -3589,7 +3597,7 @@ dependencies = [
[[package]]
name = "infra"
version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d503905#d5039059313804103f34eee49ee9844c255a99c0"
dependencies = [
"anyhow",
"bytes",
@@ -3859,7 +3867,6 @@ dependencies = [
"futures-util",
"getrandom 0.2.12",
"nanoid",
- "parking_lot 0.12.1",
"pin-project",
"protobuf",
"serde",
@@ -6179,7 +6186,7 @@ dependencies = [
[[package]]
name = "shared-entity"
version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d503905#d5039059313804103f34eee49ee9844c255a99c0"
dependencies = [
"anyhow",
"app-error",
diff --git a/frontend/appflowy_web_app/src-tauri/Cargo.toml b/frontend/appflowy_web_app/src-tauri/Cargo.toml
index 5d5dc9ec3a..10142f4acc 100644
--- a/frontend/appflowy_web_app/src-tauri/Cargo.toml
+++ b/frontend/appflowy_web_app/src-tauri/Cargo.toml
@@ -116,13 +116,13 @@ custom-protocol = ["tauri/custom-protocol"]
# To switch to the local path, run:
# scripts/tool/update_collab_source.sh
# ⚠️⚠️⚠️️
-collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" }
-collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" }
-collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" }
-collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" }
-collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" }
-collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" }
-collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" }
+collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6a5e7e49c159fcf782df84208cdb26c212c28ede" }
+collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6a5e7e49c159fcf782df84208cdb26c212c28ede" }
+collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6a5e7e49c159fcf782df84208cdb26c212c28ede" }
+collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6a5e7e49c159fcf782df84208cdb26c212c28ede" }
+collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6a5e7e49c159fcf782df84208cdb26c212c28ede" }
+collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6a5e7e49c159fcf782df84208cdb26c212c28ede" }
+collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6a5e7e49c159fcf782df84208cdb26c212c28ede" }
# Working directory: frontend
# To update the commit ID, run:
diff --git a/frontend/appflowy_web_app/src-tauri/src/init.rs b/frontend/appflowy_web_app/src-tauri/src/init.rs
index 636735e5f4..7af31af362 100644
--- a/frontend/appflowy_web_app/src-tauri/src/init.rs
+++ b/frontend/appflowy_web_app/src-tauri/src/init.rs
@@ -1,9 +1,9 @@
+use dotenv::dotenv;
use flowy_core::config::AppFlowyCoreConfig;
-use flowy_core::{AppFlowyCore, MutexAppFlowyCore, DEFAULT_NAME};
+use flowy_core::{AppFlowyCore, DEFAULT_NAME};
use lib_dispatch::runtime::AFPluginRuntime;
use std::rc::Rc;
-
-use dotenv::dotenv;
+use std::sync::{Arc, Mutex};
pub fn read_env() {
dotenv().ok();
@@ -25,7 +25,7 @@ pub fn read_env() {
}
}
-pub fn init_flowy_core() -> MutexAppFlowyCore {
+pub fn init_appflowy_core() -> MutexAppFlowyCore {
let config_json = include_str!("../tauri.conf.json");
let config: tauri_utils::config::Config = serde_json::from_str(config_json).unwrap();
@@ -61,9 +61,19 @@ pub fn init_flowy_core() -> MutexAppFlowyCore {
)
.log_filter("trace", vec!["appflowy_tauri".to_string()]);
- let runtime = Rc::new(AFPluginRuntime::new().unwrap());
+ let runtime = Arc::new(AFPluginRuntime::new().unwrap());
let cloned_runtime = runtime.clone();
runtime.block_on(async move {
MutexAppFlowyCore::new(AppFlowyCore::new(config, cloned_runtime, None).await)
})
}
+
+pub struct MutexAppFlowyCore(pub Arc