diff --git a/.github/workflows/android_ci.yaml.bak b/.github/workflows/android_ci.yaml.bak
index 8732558927d28..0cb110bae436a 100644
--- a/.github/workflows/android_ci.yaml.bak
+++ b/.github/workflows/android_ci.yaml.bak
@@ -19,7 +19,7 @@
# env:
# CARGO_TERM_COLOR: always
-# FLUTTER_VERSION: "3.19.0"
+# FLUTTER_VERSION: "3.22.0"
# RUST_TOOLCHAIN: "1.77.2"
# CARGO_MAKE_VERSION: "0.36.6"
diff --git a/.github/workflows/flutter_ci.yaml b/.github/workflows/flutter_ci.yaml
index 6ec1c766822eb..3e64bcd83f304 100644
--- a/.github/workflows/flutter_ci.yaml
+++ b/.github/workflows/flutter_ci.yaml
@@ -25,7 +25,7 @@ on:
env:
CARGO_TERM_COLOR: always
- FLUTTER_VERSION: "3.19.0"
+ FLUTTER_VERSION: "3.22.0"
RUST_TOOLCHAIN: "1.77.2"
CARGO_MAKE_VERSION: "0.36.6"
@@ -172,7 +172,7 @@ jobs:
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
sudo apt-get update
- sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev
+ sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev libmpv-dev mpv
fi
shell: bash
@@ -272,7 +272,7 @@ jobs:
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
sudo apt-get update
- sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev
+ sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev libmpv-dev mpv
shell: bash
- name: Enable Flutter Desktop
@@ -319,6 +319,12 @@ jobs:
- name: Checkout source code
uses: actions/checkout@v4
+ - name: Install video dependency
+ run: |
+ sudo apt-get update
+ sudo apt-get -y install libmpv-dev mpv
+ shell: bash
+
- name: Flutter Integration Test 1
uses: ./.github/actions/flutter_integration_test
with:
@@ -343,6 +349,12 @@ jobs:
- name: Checkout source code
uses: actions/checkout@v4
+ - name: Install video dependency
+ run: |
+ sudo apt-get update
+ sudo apt-get -y install libmpv-dev mpv
+ shell: bash
+
- name: Flutter Integration Test 2
uses: ./.github/actions/flutter_integration_test
with:
@@ -367,6 +379,12 @@ jobs:
- name: Checkout source code
uses: actions/checkout@v4
+ - name: Install video dependency
+ run: |
+ sudo apt-get update
+ sudo apt-get -y install libmpv-dev mpv
+ shell: bash
+
- name: Flutter Integration Test 3
uses: ./.github/actions/flutter_integration_test
with:
diff --git a/.github/workflows/ios_ci.yaml b/.github/workflows/ios_ci.yaml
index c32a7f93c77f1..d24eaed1f3b10 100644
--- a/.github/workflows/ios_ci.yaml
+++ b/.github/workflows/ios_ci.yaml
@@ -20,7 +20,7 @@ on:
- "!frontend/appflowy_web_app/**"
env:
- FLUTTER_VERSION: "3.19.0"
+ FLUTTER_VERSION: "3.22.0"
RUST_TOOLCHAIN: "1.77.2"
concurrency:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index ee065c6e9e933..e91d95f969ff1 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -6,7 +6,7 @@ on:
- "*"
env:
- FLUTTER_VERSION: "3.19.0"
+ FLUTTER_VERSION: "3.22.0"
RUST_TOOLCHAIN: "1.77.2"
jobs:
diff --git a/.github/workflows/rust_coverage.yml b/.github/workflows/rust_coverage.yml
index 4d8e9cbad87d9..12e728698fc8d 100644
--- a/.github/workflows/rust_coverage.yml
+++ b/.github/workflows/rust_coverage.yml
@@ -10,7 +10,7 @@ on:
env:
CARGO_TERM_COLOR: always
- FLUTTER_VERSION: "3.19.0"
+ FLUTTER_VERSION: "3.22.0"
RUST_TOOLCHAIN: "1.77.2"
jobs:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e612d832f79d2..0cc8d05b68553 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -105,7 +105,7 @@
- Fixed a bug where newly created rows were not being automatically sorted.
- Fixed issues related to deleting a sorting field or sort not removing existing sorts properly.
### Notes
-- Windows 7, Windows 8, and iOS 11 are not yet supported due to the upgrade to Flutter 3.19.0.
+- Windows 7, Windows 8, and iOS 11 are not yet supported due to the upgrade to Flutter 3.22.0.
## Version 0.4.9 - 02/17/2024
### Bug Fixes
diff --git a/frontend/Makefile.toml b/frontend/Makefile.toml
index 5a962f3cbf590..a71ffec1fcf34 100644
--- a/frontend/Makefile.toml
+++ b/frontend/Makefile.toml
@@ -26,7 +26,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
CARGO_MAKE_CRATE_NAME = "dart-ffi"
LIB_NAME = "dart_ffi"
-APPFLOWY_VERSION = "0.5.8"
+APPFLOWY_VERSION = "0.5.9"
FLUTTER_DESKTOP_FEATURES = "dart"
PRODUCT_NAME = "AppFlowy"
MACOSX_DEPLOYMENT_TARGET = "11.0"
diff --git a/frontend/appflowy_flutter/android/app/src/main/AndroidManifest.xml b/frontend/appflowy_flutter/android/app/src/main/AndroidManifest.xml
index 351994354de95..279b17320c5fe 100644
--- a/frontend/appflowy_flutter/android/app/src/main/AndroidManifest.xml
+++ b/frontend/appflowy_flutter/android/app/src/main/AndroidManifest.xml
@@ -58,4 +58,11 @@
+
+
+
\ No newline at end of file
diff --git a/frontend/appflowy_flutter/integration_test/cloud/workspace/collaborative_workspace_test.dart b/frontend/appflowy_flutter/integration_test/cloud/workspace/collaborative_workspace_test.dart
index 11af75af1d17e..9886c2228e315 100644
--- a/frontend/appflowy_flutter/integration_test/cloud/workspace/collaborative_workspace_test.dart
+++ b/frontend/appflowy_flutter/integration_test/cloud/workspace/collaborative_workspace_test.dart
@@ -2,8 +2,6 @@
import 'dart:io';
-import 'package:flutter/material.dart';
-
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/loading.dart';
@@ -12,14 +10,15 @@ import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart';
-import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_workspace.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart';
+import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
import 'package:appflowy/workspace/presentation/widgets/user_avatar.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/uuid.dart';
+import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:path/path.dart' as p;
diff --git a/frontend/appflowy_flutter/integration_test/desktop/board/board_test_runner.dart b/frontend/appflowy_flutter/integration_test/desktop/board/board_test_runner.dart
index 932c266bda0ac..a786367fff2cc 100644
--- a/frontend/appflowy_flutter/integration_test/desktop/board/board_test_runner.dart
+++ b/frontend/appflowy_flutter/integration_test/desktop/board/board_test_runner.dart
@@ -1,10 +1,10 @@
import 'package:integration_test/integration_test.dart';
-import 'board_row_test.dart' as board_row_test;
import 'board_add_row_test.dart' as board_add_row_test;
import 'board_group_test.dart' as board_group_test;
+import 'board_row_test.dart' as board_row_test;
-void startTesting() {
+void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
// Board integration tests
diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_alignment_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_alignment_test.dart
index de7401652e170..d95d907881e92 100644
--- a/frontend/appflowy_flutter/integration_test/desktop/document/document_alignment_test.dart
+++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_alignment_test.dart
@@ -66,6 +66,7 @@ void main() {
LogicalKeyboardKey.keyR,
],
tester: tester,
+ withKeyUp: true,
);
expect(first.attributes[blockComponentAlign], rightAlignmentKey);
@@ -77,6 +78,7 @@ void main() {
LogicalKeyboardKey.keyE,
],
tester: tester,
+ withKeyUp: true,
);
expect(first.attributes[blockComponentAlign], centerAlignmentKey);
@@ -88,6 +90,7 @@ void main() {
LogicalKeyboardKey.keyL,
],
tester: tester,
+ withKeyUp: true,
);
expect(first.attributes[blockComponentAlign], leftAlignmentKey);
});
diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_inline_page_reference_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_inline_page_reference_test.dart
index 0bbd64c82b836..f1699108405cf 100644
--- a/frontend/appflowy_flutter/integration_test/desktop/document/document_inline_page_reference_test.dart
+++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_inline_page_reference_test.dart
@@ -111,6 +111,7 @@ Future triggerReferenceDocumentBySlashMenu(WidgetTester tester) async {
LogicalKeyboardKey.enter,
],
tester: tester,
+ withKeyUp: true,
);
await tester.pumpAndSettle();
@@ -129,6 +130,7 @@ Future enterDocumentText(WidgetTester tester) async {
LogicalKeyboardKey.keyT,
],
tester: tester,
+ withKeyUp: true,
);
await tester.pumpAndSettle();
}
diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_more_actions_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_more_actions_test.dart
new file mode 100644
index 0000000000000..d4cc11d7f0518
--- /dev/null
+++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_more_actions_test.dart
@@ -0,0 +1,34 @@
+import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:integration_test/integration_test.dart';
+
+import '../../shared/util.dart';
+
+void main() {
+ IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+ group('MoreViewActions', () {
+ testWidgets('can duplicate and delete from menu', (tester) async {
+ await tester.initializeAppFlowy();
+ await tester.tapAnonymousSignInButton();
+ await tester.pumpAndSettle();
+
+ final pageFinder = find.byType(ViewItem);
+ expect(pageFinder, findsNWidgets(1));
+
+ // Duplicate
+ await tester.openMoreViewActions();
+ await tester.duplicateByMoreViewActions();
+ await tester.pumpAndSettle();
+
+ expect(pageFinder, findsNWidgets(2));
+
+ // Delete
+ await tester.openMoreViewActions();
+ await tester.deleteByMoreViewActions();
+ await tester.pumpAndSettle();
+
+ expect(pageFinder, findsNWidgets(1));
+ });
+ });
+}
diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_test_runner.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_test_runner.dart
index 42462c26585a3..239e7e09a8e23 100644
--- a/frontend/appflowy_flutter/integration_test/desktop/document/document_test_runner.dart
+++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_test_runner.dart
@@ -6,6 +6,9 @@ import 'document_copy_and_paste_test.dart' as document_copy_and_paste_test;
import 'document_create_and_delete_test.dart'
as document_create_and_delete_test;
import 'document_option_action_test.dart' as document_option_action_test;
+import 'document_inline_page_reference_test.dart'
+ as document_inline_page_reference_test;
+import 'document_more_actions_test.dart' as document_more_actions_test;
import 'document_text_direction_test.dart' as document_text_direction_test;
import 'document_with_cover_image_test.dart' as document_with_cover_image_test;
import 'document_with_database_test.dart' as document_with_database_test;
@@ -16,8 +19,6 @@ import 'document_with_inline_page_test.dart' as document_with_inline_page_test;
import 'document_with_outline_block_test.dart' as document_with_outline_block;
import 'document_with_toggle_list_test.dart' as document_with_toggle_list_test;
import 'edit_document_test.dart' as document_edit_test;
-import 'document_inline_page_reference_test.dart'
- as document_inline_page_reference_test;
void startTesting() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
@@ -38,4 +39,5 @@ void startTesting() {
document_option_action_test.main();
document_with_image_block_test.main();
document_inline_page_reference_test.main();
+ document_more_actions_test.main();
}
diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_cover_image_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_cover_image_test.dart
index 2b42ef74513ad..f7a88d7bec54b 100644
--- a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_cover_image_test.dart
+++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_cover_image_test.dart
@@ -130,24 +130,24 @@ void main() {
final searchEmojiTextField = find.byWidgetPredicate(
(widget) =>
widget is TextField &&
- widget.decoration!.hintText == LocaleKeys.emoji_search.tr(),
+ widget.decoration!.hintText == LocaleKeys.search_label.tr(),
);
await tester.enterText(
searchEmojiTextField,
- 'hand',
+ 'punch',
);
// change skin tone
await tester.editor.changeEmojiSkinTone(EmojiSkinTone.dark);
// select an icon with skin tone
- const hand = '👋🏿';
- await tester.tapEmoji(hand);
- tester.expectToSeeDocumentIcon(hand);
+ const punch = '👊🏿';
+ await tester.tapEmoji(punch);
+ tester.expectToSeeDocumentIcon(punch);
tester.expectViewHasIcon(
gettingStarted,
ViewLayoutPB.Document,
- hand,
+ punch,
);
});
});
diff --git a/frontend/appflowy_flutter/integration_test/desktop/settings/notifications_settings_test.dart b/frontend/appflowy_flutter/integration_test/desktop/settings/notifications_settings_test.dart
index 46550aa81a4f4..958910dd80e58 100644
--- a/frontend/appflowy_flutter/integration_test/desktop/settings/notifications_settings_test.dart
+++ b/frontend/appflowy_flutter/integration_test/desktop/settings/notifications_settings_test.dart
@@ -8,8 +8,8 @@ import '../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
- group('board add row test', () {
- testWidgets('Add card from header', (tester) async {
+ group('notification test', () {
+ testWidgets('enable notification', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
@@ -17,7 +17,7 @@ void main() {
await tester.openSettingsPage(SettingsPage.notifications);
await tester.pumpAndSettle();
- final switchFinder = find.byType(Switch);
+ final switchFinder = find.byType(Switch).first;
// Defaults to enabled
Switch switchWidget = tester.widget(switchFinder);
diff --git a/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_expand_test.dart b/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_expand_test.dart
index ff0df1c7dac1a..f2a1fae8ae9f2 100644
--- a/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_expand_test.dart
+++ b/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_expand_test.dart
@@ -1,6 +1,6 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
-import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_folder.dart';
+import 'package:appflowy/workspace/presentation/home/menu/sidebar/shared/sidebar_folder.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_test/flutter_test.dart';
@@ -12,8 +12,8 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('sidebar expand test', () {
- bool isExpanded({required FolderCategoryType type}) {
- if (type == FolderCategoryType.private) {
+ bool isExpanded({required FolderSpaceType type}) {
+ if (type == FolderSpaceType.private) {
return find
.descendant(
of: find.byType(PrivateSectionFolder),
@@ -30,19 +30,19 @@ void main() {
await tester.tapAnonymousSignInButton();
// first time is expanded
- expect(isExpanded(type: FolderCategoryType.private), true);
+ expect(isExpanded(type: FolderSpaceType.private), true);
// collapse the personal folder
await tester.tapButton(
find.byTooltip(LocaleKeys.sideBar_clickToHidePrivate.tr()),
);
- expect(isExpanded(type: FolderCategoryType.private), false);
+ expect(isExpanded(type: FolderSpaceType.private), false);
// expand the personal folder
await tester.tapButton(
find.byTooltip(LocaleKeys.sideBar_clickToHidePrivate.tr()),
);
- expect(isExpanded(type: FolderCategoryType.private), true);
+ expect(isExpanded(type: FolderSpaceType.private), true);
});
});
}
diff --git a/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_favorites_test.dart b/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_favorites_test.dart
index 072764217c862..729ee62a3ed40 100644
--- a/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_favorites_test.dart
+++ b/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_favorites_test.dart
@@ -1,5 +1,5 @@
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
-import 'package:appflowy/workspace/presentation/home/menu/sidebar/folder/_favorite_folder.dart';
+import 'package:appflowy/workspace/presentation/home/menu/sidebar/favorites/favorite_folder.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
@@ -46,7 +46,7 @@ void main() {
await tester.favoriteViewByName(names[1]);
expect(
tester.findFavoritePageName(names[1]),
- findsNWidgets(2),
+ findsNWidgets(1),
);
await tester.unfavoriteViewByName(gettingStarted);
@@ -120,9 +120,9 @@ void main() {
(widget) =>
widget is SingleInnerViewItem &&
widget.view.isFavorite &&
- widget.categoryType == FolderCategoryType.favorite,
+ widget.spaceType == FolderSpaceType.favorite,
),
- findsNWidgets(6),
+ findsNWidgets(3),
);
await tester.hoverOnPageName(
@@ -135,7 +135,7 @@ void main() {
expect(
tester.findAllFavoritePages(),
- findsNWidgets(3),
+ findsNWidgets(2),
);
await tester.hoverOnPageName(
@@ -168,7 +168,7 @@ void main() {
widget.isSelected != null &&
widget.isSelected!(),
),
- findsNWidgets(2),
+ findsNWidgets(1),
);
},
);
diff --git a/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_test_runner.dart b/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_test_runner.dart
index 35bcf599ab735..3bc41d78c0d55 100644
--- a/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_test_runner.dart
+++ b/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_test_runner.dart
@@ -4,7 +4,7 @@ import 'sidebar_favorites_test.dart' as sidebar_favorite_test;
import 'sidebar_icon_test.dart' as sidebar_icon_test;
import 'sidebar_test.dart' as sidebar_test;
-void startTesting() {
+void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
// Sidebar integration tests
diff --git a/frontend/appflowy_flutter/integration_test/desktop_runner_3.dart b/frontend/appflowy_flutter/integration_test/desktop_runner_3.dart
index 72bf8a4faef65..f1025b8f1e92d 100644
--- a/frontend/appflowy_flutter/integration_test/desktop_runner_3.dart
+++ b/frontend/appflowy_flutter/integration_test/desktop_runner_3.dart
@@ -27,7 +27,7 @@ Future runIntegration3OnDesktop() async {
settings_test_runner.main();
share_markdown_test.main();
import_files_test.main();
- sidebar_test_runner.startTesting();
- board_test_runner.startTesting();
+ sidebar_test_runner.main();
+ board_test_runner.main();
tabs_test.main();
}
diff --git a/frontend/appflowy_flutter/integration_test/shared/common_operations.dart b/frontend/appflowy_flutter/integration_test/shared/common_operations.dart
index 08d6fd0ec6217..8bc72369943a8 100644
--- a/frontend/appflowy_flutter/integration_test/shared/common_operations.dart
+++ b/frontend/appflowy_flutter/integration_test/shared/common_operations.dart
@@ -11,9 +11,9 @@ import 'package:appflowy/shared/feature_flags.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/presentation/screens/screens.dart';
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
-import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_new_page_button.dart';
-import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_workspace.dart';
+import 'package:appflowy/workspace/presentation/home/menu/sidebar/shared/sidebar_new_page_button.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart';
+import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/draggable_view_item.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_add_button.dart';
@@ -22,6 +22,8 @@ import 'package:appflowy/workspace/presentation/notifications/widgets/flowy_tab.
import 'package:appflowy/workspace/presentation/notifications/widgets/notification_button.dart';
import 'package:appflowy/workspace/presentation/notifications/widgets/notification_tab_bar.dart';
import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart';
+import 'package:appflowy/workspace/presentation/widgets/more_view_actions/more_view_actions.dart';
+import 'package:appflowy/workspace/presentation/widgets/more_view_actions/widgets/common_view_action.dart';
import 'package:appflowy/workspace/presentation/widgets/view_title_bar.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
@@ -57,6 +59,7 @@ extension CommonOperations on WidgetTester {
/// Tap the + button on the home page.
Future tapAddViewButton({
String name = gettingStarted,
+ ViewLayoutPB layout = ViewLayoutPB.Document,
}) async {
await hoverOnPageName(
name,
@@ -276,7 +279,7 @@ extension CommonOperations on WidgetTester {
bool openAfterCreated = true,
}) async {
// create a new page
- await tapAddViewButton(name: parentName ?? gettingStarted);
+ await tapAddViewButton(name: parentName ?? gettingStarted, layout: layout);
await tapButtonWithName(layout.menuName);
final settingsOrFailure = await getIt().getWithFormat(
KVKeys.showRenameDialogWhenCreatingNewFile,
@@ -564,6 +567,44 @@ extension CommonOperations on WidgetTester {
);
await tapButton(button);
}
+
+ Future openMoreViewActions() async {
+ final button = find.byType(MoreViewActions);
+ await tap(button);
+ await pumpAndSettle();
+ }
+
+ /// Presses on the Duplicate ViewAction in the [MoreViewActions] popup.
+ ///
+ /// [openMoreViewActions] must be called beforehand!
+ ///
+ Future duplicateByMoreViewActions() async {
+ final button = find.descendant(
+ of: find.byType(ListView),
+ matching: find.byWidgetPredicate(
+ (widget) =>
+ widget is ViewAction && widget.type == ViewActionType.duplicate,
+ ),
+ );
+ await tap(button);
+ await pump();
+ }
+
+ /// Presses on the Delete ViewAction in the [MoreViewActions] popup.
+ ///
+ /// [openMoreViewActions] must be called beforehand!
+ ///
+ Future deleteByMoreViewActions() async {
+ final button = find.descendant(
+ of: find.byType(ListView),
+ matching: find.byWidgetPredicate(
+ (widget) =>
+ widget is ViewAction && widget.type == ViewActionType.delete,
+ ),
+ );
+ await tap(button);
+ await pump();
+ }
}
extension SettingsFinder on CommonFinders {
diff --git a/frontend/appflowy_flutter/integration_test/shared/editor_test_operations.dart b/frontend/appflowy_flutter/integration_test/shared/editor_test_operations.dart
index 0bdcf06367bbe..4eff62321a4b7 100644
--- a/frontend/appflowy_flutter/integration_test/shared/editor_test_operations.dart
+++ b/frontend/appflowy_flutter/integration_test/shared/editor_test_operations.dart
@@ -81,15 +81,12 @@ class EditorOperations {
/// Taps the 'Remove Icon' button in the cover toolbar and the icon popover
Future tapRemoveIconButton({bool isInPicker = false}) async {
- Finder button =
- find.text(LocaleKeys.document_plugins_cover_removeIcon.tr());
- if (isInPicker) {
- button = find.descendant(
- of: find.byType(FlowyIconPicker),
- matching: button,
- );
- }
-
+ final Finder button = !isInPicker
+ ? find.text(LocaleKeys.document_plugins_cover_removeIcon.tr())
+ : find.descendant(
+ of: find.byType(FlowyIconPicker),
+ matching: find.text(LocaleKeys.button_remove.tr()),
+ );
await tester.tapButton(button);
}
diff --git a/frontend/appflowy_flutter/integration_test/shared/expectation.dart b/frontend/appflowy_flutter/integration_test/shared/expectation.dart
index aeb3f04cb8873..c4b54a0fda5de 100644
--- a/frontend/appflowy_flutter/integration_test/shared/expectation.dart
+++ b/frontend/appflowy_flutter/integration_test/shared/expectation.dart
@@ -165,7 +165,7 @@ extension Expectation on WidgetTester {
(widget) =>
widget is SingleInnerViewItem &&
widget.view.isFavorite &&
- widget.categoryType == FolderCategoryType.favorite &&
+ widget.spaceType == FolderSpaceType.favorite &&
widget.view.name == name &&
widget.view.layout == layout,
skipOffstage: false,
@@ -175,7 +175,7 @@ extension Expectation on WidgetTester {
(widget) =>
widget is SingleInnerViewItem &&
widget.view.isFavorite &&
- widget.categoryType == FolderCategoryType.favorite,
+ widget.spaceType == FolderSpaceType.favorite,
);
Finder findPageName(
diff --git a/frontend/appflowy_flutter/integration_test/shared/keyboard.dart b/frontend/appflowy_flutter/integration_test/shared/keyboard.dart
index d792b92c662b2..567e7e548cf7f 100644
--- a/frontend/appflowy_flutter/integration_test/shared/keyboard.dart
+++ b/frontend/appflowy_flutter/integration_test/shared/keyboard.dart
@@ -5,10 +5,18 @@ class FlowyTestKeyboard {
static Future simulateKeyDownEvent(
List keys, {
required flutter_test.WidgetTester tester,
+ bool withKeyUp = false,
}) async {
for (final LogicalKeyboardKey key in keys) {
await flutter_test.simulateKeyDownEvent(key);
await tester.pumpAndSettle();
}
+
+ if (withKeyUp) {
+ for (final LogicalKeyboardKey key in keys) {
+ await flutter_test.simulateKeyUpEvent(key);
+ await tester.pumpAndSettle();
+ }
+ }
}
}
diff --git a/frontend/appflowy_flutter/integration_test/shared/settings.dart b/frontend/appflowy_flutter/integration_test/shared/settings.dart
index dd13bd088f750..3b25c32111341 100644
--- a/frontend/appflowy_flutter/integration_test/shared/settings.dart
+++ b/frontend/appflowy_flutter/integration_test/shared/settings.dart
@@ -1,7 +1,7 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart';
-import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_setting.dart';
+import 'package:appflowy/workspace/presentation/home/menu/sidebar/shared/sidebar_setting.dart';
import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.dart';
import 'package:appflowy/workspace/presentation/settings/pages/settings_workspace_view.dart';
import 'package:appflowy/workspace/presentation/settings/settings_dialog.dart';
@@ -12,7 +12,6 @@ import 'package:flowy_infra_ui/style_widget/text_field.dart';
import 'package:flutter_test/flutter_test.dart';
import '../desktop/board/board_hide_groups_test.dart';
-
import 'base.dart';
import 'common_operations.dart';
diff --git a/frontend/appflowy_flutter/integration_test/shared/workspace.dart b/frontend/appflowy_flutter/integration_test/shared/workspace.dart
index 5137944364957..4d20d88ce193f 100644
--- a/frontend/appflowy_flutter/integration_test/shared/workspace.dart
+++ b/frontend/appflowy_flutter/integration_test/shared/workspace.dart
@@ -1,14 +1,14 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/base/icon/icon_picker.dart';
-import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_workspace.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_icon.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart';
+import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
-import 'base.dart';
+import 'util.dart';
extension AppFlowyWorkspace on WidgetTester {
/// Open workspace menu
@@ -36,12 +36,19 @@ extension AppFlowyWorkspace on WidgetTester {
matching: find.byType(WorkspaceMoreActionList),
);
expect(moreButton, findsOneWidget);
- await tapButton(moreButton);
- await tapButton(find.findTextInFlowyText(LocaleKeys.button_rename.tr()));
- final input = find.byType(TextFormField);
- expect(input, findsOneWidget);
- await enterText(input, name);
- await tapButton(find.text(LocaleKeys.button_ok.tr()));
+ await hoverOnWidget(
+ moreButton,
+ onHover: () async {
+ await tapButton(moreButton);
+ await tapButton(
+ find.findTextInFlowyText(LocaleKeys.button_rename.tr()),
+ );
+ final input = find.byType(TextFormField);
+ expect(input, findsOneWidget);
+ await enterText(input, name);
+ await tapButton(find.text(LocaleKeys.button_ok.tr()));
+ },
+ );
}
Future changeWorkspaceIcon(String icon) async {
diff --git a/frontend/appflowy_flutter/ios/Podfile.lock b/frontend/appflowy_flutter/ios/Podfile.lock
index e62299792dc3a..5a2d069c36e5b 100644
--- a/frontend/appflowy_flutter/ios/Podfile.lock
+++ b/frontend/appflowy_flutter/ios/Podfile.lock
@@ -58,6 +58,12 @@ PODS:
- Flutter
- keyboard_height_plugin (0.0.1):
- Flutter
+ - media_kit_libs_ios_video (1.0.4):
+ - Flutter
+ - media_kit_native_event_loop (1.0.0):
+ - Flutter
+ - media_kit_video (0.0.1):
+ - Flutter
- package_info_plus (0.4.5):
- Flutter
- path_provider_foundation (0.0.1):
@@ -66,6 +72,8 @@ PODS:
- permission_handler_apple (9.3.0):
- Flutter
- ReachabilitySwift (5.0.0)
+ - screen_brightness_ios (0.1.0):
+ - Flutter
- SDWebImage (5.14.2):
- SDWebImage/Core (= 5.14.2)
- SDWebImage/Core (5.14.2)
@@ -83,6 +91,10 @@ PODS:
- Toast (4.0.0)
- url_launcher_ios (0.0.1):
- Flutter
+ - volume_controller (0.0.1):
+ - Flutter
+ - wakelock_plus (0.0.1):
+ - Flutter
DEPENDENCIES:
- app_links (from `.symlinks/plugins/app_links/ios`)
@@ -98,14 +110,20 @@ DEPENDENCIES:
- integration_test (from `.symlinks/plugins/integration_test/ios`)
- irondash_engine_context (from `.symlinks/plugins/irondash_engine_context/ios`)
- keyboard_height_plugin (from `.symlinks/plugins/keyboard_height_plugin/ios`)
+ - media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
+ - media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
+ - media_kit_video (from `.symlinks/plugins/media_kit_video/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
+ - screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/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`)
- super_native_extensions (from `.symlinks/plugins/super_native_extensions/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
+ - volume_controller (from `.symlinks/plugins/volume_controller/ios`)
+ - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
SPEC REPOS:
trunk:
@@ -143,12 +161,20 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/irondash_engine_context/ios"
keyboard_height_plugin:
:path: ".symlinks/plugins/keyboard_height_plugin/ios"
+ media_kit_libs_ios_video:
+ :path: ".symlinks/plugins/media_kit_libs_ios_video/ios"
+ media_kit_native_event_loop:
+ :path: ".symlinks/plugins/media_kit_native_event_loop/ios"
+ media_kit_video:
+ :path: ".symlinks/plugins/media_kit_video/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
+ screen_brightness_ios:
+ :path: ".symlinks/plugins/screen_brightness_ios/ios"
share_plus:
:path: ".symlinks/plugins/share_plus/ios"
shared_preferences_foundation:
@@ -159,6 +185,10 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/super_native_extensions/ios"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
+ volume_controller:
+ :path: ".symlinks/plugins/volume_controller/ios"
+ wakelock_plus:
+ :path: ".symlinks/plugins/wakelock_plus/ios"
SPEC CHECKSUMS:
app_links: 5ef33d0d295a89d9d16bb81b0e3b0d5f70d6c875
@@ -173,13 +203,17 @@ SPEC CHECKSUMS:
fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb
image_picker_ios: 99dfe1854b4fa34d0364e74a78448a0151025425
- integration_test: 13825b8a9334a850581300559b8839134b124670
+ integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4
irondash_engine_context: 3458bf979b90d616ffb8ae03a150bafe2e860cc9
keyboard_height_plugin: 43fa8bba20fd5c4fdeed5076466b8b9d43cc6b86
+ media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
+ media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
+ media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
+ screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
SDWebImage: b9a731e1d6307f44ca703b3976d18c24ca561e84
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
@@ -188,6 +222,8 @@ SPEC CHECKSUMS:
SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812
+ volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
+ wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1
PODFILE CHECKSUM: d0d9b4ff572d8695c38eb3f9b490f55cdfc57eca
diff --git a/frontend/appflowy_flutter/ios/Runner/Info.plist b/frontend/appflowy_flutter/ios/Runner/Info.plist
index 8c605b9d3a026..5ec528b05e431 100644
--- a/frontend/appflowy_flutter/ios/Runner/Info.plist
+++ b/frontend/appflowy_flutter/ios/Runner/Info.plist
@@ -66,5 +66,10 @@
UIViewControllerBasedStatusBarAppearance
+ NSAppTransportSecurity
+
+ NSAllowsArbitraryLoads
+
+
\ No newline at end of file
diff --git a/frontend/appflowy_flutter/lib/core/config/kv_keys.dart b/frontend/appflowy_flutter/lib/core/config/kv_keys.dart
index ff97b6124111e..00e79153e47ce 100644
--- a/frontend/appflowy_flutter/lib/core/config/kv_keys.dart
+++ b/frontend/appflowy_flutter/lib/core/config/kv_keys.dart
@@ -65,6 +65,11 @@ class KVKeys {
/// {'feature_flag_1': true, 'feature_flag_2': false}
static const String featureFlag = 'featureFlag';
+ /// The key for saving show notification icon option
+ ///
+ /// The value is a boolean string
+ static const String showNotificationIcon = 'showNotificationIcon';
+
/// The key for saving the last opened workspace id
///
/// The workspace id is a string.
@@ -75,4 +80,15 @@ class KVKeys {
///
/// The value is a double string.
static const String scaleFactor = 'scaleFactor';
+
+ /// The key for saving the last opened space
+ ///
+ /// The value is a int string.
+ static const String lastOpenedSpace = 'lastOpenedSpace';
+
+ /// The key for saving the space order
+ ///
+ /// The value is a json string with the following format:
+ /// [0, 1, 2]
+ static const String spaceOrder = 'spaceOrder';
}
diff --git a/frontend/appflowy_flutter/lib/core/frameless_window.dart b/frontend/appflowy_flutter/lib/core/frameless_window.dart
index 7877615a9878d..fcd955fc93879 100644
--- a/frontend/appflowy_flutter/lib/core/frameless_window.dart
+++ b/frontend/appflowy_flutter/lib/core/frameless_window.dart
@@ -3,12 +3,13 @@ import 'dart:io';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/shared/window_title_bar.dart';
+import 'package:appflowy/util/theme_extension.dart';
import 'package:appflowy/workspace/application/home/home_setting_bloc.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
-import 'package:flutter/services.dart';
import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class CocoaWindowChannel {
@@ -102,13 +103,25 @@ class MoveWindowDetectorState extends State {
return const SizedBox.shrink();
}
+ final color = Theme.of(context).isLightMode ? Colors.white : Colors.black;
+ final textSpan = TextSpan(
+ children: [
+ TextSpan(
+ text: '${LocaleKeys.sideBar_openSidebar.tr()}\n',
+ style: Theme.of(context).textTheme.bodyMedium!.copyWith(color: color),
+ ),
+ TextSpan(
+ text: Platform.isMacOS ? '⌘+.' : 'Ctrl+\\',
+ style: Theme.of(context)
+ .textTheme
+ .bodyMedium!
+ .copyWith(color: Theme.of(context).hintColor),
+ ),
+ ],
+ );
+
return FlowyTooltip(
- richMessage: TextSpan(
- children: [
- TextSpan(text: '${LocaleKeys.sideBar_closeSidebar.tr()}\n'),
- const TextSpan(text: 'Ctrl+\\'),
- ],
- ),
+ richMessage: textSpan,
child: FlowyIconButton(
hoverColor: Colors.transparent,
onPressed: () => context
diff --git a/frontend/appflowy_flutter/lib/flutter/af_dropdown_menu.dart b/frontend/appflowy_flutter/lib/flutter/af_dropdown_menu.dart
index da9f4649c36ae..9d18bb14f7e97 100644
--- a/frontend/appflowy_flutter/lib/flutter/af_dropdown_menu.dart
+++ b/frontend/appflowy_flutter/lib/flutter/af_dropdown_menu.dart
@@ -481,7 +481,7 @@ class _AFDropdownMenuState extends State> {
ButtonStyle effectiveStyle = entry.style ?? defaultStyle;
final Color focusedBackgroundColor = effectiveStyle.foregroundColor
- ?.resolve({MaterialState.focused}) ??
+ ?.resolve({WidgetState.focused}) ??
Theme.of(context).colorScheme.onSurface;
Widget label = entry.labelWidget ?? Text(entry.label);
@@ -499,7 +499,7 @@ class _AFDropdownMenuState extends State> {
// color will also change to foregroundColor.withOpacity(0.12).
effectiveStyle = entry.enabled && i == focusedIndex
? effectiveStyle.copyWith(
- backgroundColor: MaterialStatePropertyAll(
+ backgroundColor: WidgetStatePropertyAll(
focusedBackgroundColor.withOpacity(0.12),
),
)
@@ -628,17 +628,17 @@ class _AFDropdownMenuState extends State> {
final double? anchorWidth = getWidth(_anchorKey);
if (widget.width != null) {
effectiveMenuStyle = effectiveMenuStyle.copyWith(
- minimumSize: MaterialStatePropertyAll(Size(widget.width!, 0.0)),
+ minimumSize: WidgetStatePropertyAll(Size(widget.width!, 0.0)),
);
} else if (anchorWidth != null) {
effectiveMenuStyle = effectiveMenuStyle.copyWith(
- minimumSize: MaterialStatePropertyAll(Size(anchorWidth, 0.0)),
+ minimumSize: WidgetStatePropertyAll(Size(anchorWidth, 0.0)),
);
}
if (widget.menuHeight != null) {
effectiveMenuStyle = effectiveMenuStyle.copyWith(
- maximumSize: MaterialStatePropertyAll(
+ maximumSize: WidgetStatePropertyAll(
Size(double.infinity, widget.menuHeight!),
),
);
@@ -1029,8 +1029,8 @@ class _DropdownMenuDefaultsM3 extends DropdownMenuThemeData {
@override
MenuStyle get menuStyle {
return const MenuStyle(
- minimumSize: MaterialStatePropertyAll(Size(_kMinimumWidth, 0.0)),
- maximumSize: MaterialStatePropertyAll(Size.infinite),
+ minimumSize: WidgetStatePropertyAll(Size(_kMinimumWidth, 0.0)),
+ maximumSize: WidgetStatePropertyAll(Size.infinite),
visualDensity: VisualDensity.standard,
);
}
diff --git a/frontend/appflowy_flutter/lib/main.dart b/frontend/appflowy_flutter/lib/main.dart
index 9f140489c41ed..bee524b5742a4 100644
--- a/frontend/appflowy_flutter/lib/main.dart
+++ b/frontend/appflowy_flutter/lib/main.dart
@@ -1,9 +1,12 @@
import 'package:scaled_app/scaled_app.dart';
+import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
+
import 'startup/startup.dart';
Future main() async {
ScaledWidgetsFlutterBinding.ensureInitialized(scaleFactor: (_) => 1.0);
+ VideoBlockKit.ensureInitialized();
await runAppFlowy();
}
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 410bc68c4e325..547c81f00b988 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
@@ -57,7 +57,19 @@ class RecentViewBloc extends Bloc {
}
},
);
+
+ // only document supports the cover
+ if (view.layout != ViewLayoutPB.Document) {
+ emit(
+ state.copyWith(
+ name: view.name,
+ icon: view.icon.value,
+ ),
+ );
+ }
+
final cover = getCoverV2();
+
if (cover != null) {
emit(
state.copyWith(
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/base/type_option_menu_item.dart b/frontend/appflowy_flutter/lib/mobile/presentation/base/type_option_menu_item.dart
index 04149c823814a..497f76935444d 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/base/type_option_menu_item.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/base/type_option_menu_item.dart
@@ -1,5 +1,5 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
-import 'package:flowy_infra_ui/flowy_infra_ui.dart' hide WidgetBuilder;
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
class TypeOptionMenuItemValue {
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/base/view_page/app_bar_buttons.dart b/frontend/appflowy_flutter/lib/mobile/presentation/base/view_page/app_bar_buttons.dart
index d712aa5aecc98..1301719c41f30 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/base/view_page/app_bar_buttons.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/base/view_page/app_bar_buttons.dart
@@ -12,6 +12,7 @@ import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
import 'package:appflowy/workspace/application/view/prelude.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra/theme_extension.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -92,7 +93,7 @@ class MobileViewPageMoreButton extends StatelessWidget {
context,
showDragHandle: true,
showDivider: false,
- backgroundColor: Theme.of(context).colorScheme.background,
+ backgroundColor: AFThemeExtension.of(context).background,
builder: (_) => MultiBlocProvider(
providers: [
BlocProvider.value(value: context.read()),
@@ -144,7 +145,7 @@ class MobileViewPageLayoutButton extends StatelessWidget {
showDoneButton: true,
showHeader: true,
title: LocaleKeys.pageStyle_title.tr(),
- backgroundColor: Theme.of(context).colorScheme.background,
+ backgroundColor: AFThemeExtension.of(context).background,
builder: (_) => MultiBlocProvider(
providers: [
BlocProvider.value(value: context.read()),
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/base/view_page/more_bottom_sheet.dart b/frontend/appflowy_flutter/lib/mobile/presentation/base/view_page/more_bottom_sheet.dart
index b94242eceb232..6394ca9647193 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/base/view_page/more_bottom_sheet.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/base/view_page/more_bottom_sheet.dart
@@ -38,6 +38,7 @@ class MobileViewPageMoreBottomSheet extends StatelessWidget {
case MobileViewBottomSheetBodyAction.removeFromFavorites:
context.pop();
context.read().add(FavoriteEvent.toggle(view));
+
break;
case MobileViewBottomSheetBodyAction.undo:
EditorNotification.undo().post();
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_action_widget.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_action_widget.dart
index 3e594b47f9307..6b54b1fda3b0d 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_action_widget.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_action_widget.dart
@@ -1,4 +1,5 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
+import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
@@ -19,7 +20,7 @@ class BottomSheetActionWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final iconColor =
- this.iconColor ?? Theme.of(context).colorScheme.onBackground;
+ this.iconColor ?? AFThemeExtension.of(context).onBackground;
if (svg == null) {
return OutlinedButton(
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_add_new_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_add_new_page.dart
index 37a5cb0221bac..18c5c3a6df3ff 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_add_new_page.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_add_new_page.dart
@@ -23,7 +23,7 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget {
text: LocaleKeys.document_menuName.tr(),
leftIcon: const FlowySvg(
FlowySvgs.document_s,
- size: Size.square(20),
+ size: Size.square(18),
),
showTopBorder: false,
onTap: () => onAction(ViewLayoutPB.Document),
@@ -32,7 +32,7 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget {
text: LocaleKeys.grid_menuName.tr(),
leftIcon: const FlowySvg(
FlowySvgs.grid_s,
- size: Size.square(20),
+ size: Size.square(18),
),
showTopBorder: false,
onTap: () => onAction(ViewLayoutPB.Grid),
@@ -41,7 +41,7 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget {
text: LocaleKeys.board_menuName.tr(),
leftIcon: const FlowySvg(
FlowySvgs.board_s,
- size: Size.square(20),
+ size: Size.square(18),
),
showTopBorder: false,
onTap: () => onAction(ViewLayoutPB.Board),
@@ -49,8 +49,8 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget {
FlowyOptionTile.text(
text: LocaleKeys.calendar_menuName.tr(),
leftIcon: const FlowySvg(
- FlowySvgs.date_s,
- size: Size.square(20),
+ FlowySvgs.calendar_s,
+ size: Size.square(18),
),
showTopBorder: false,
onTap: () => onAction(ViewLayoutPB.Calendar),
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 c1e2560e48421..75b0151a3a518 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
@@ -1,9 +1,17 @@
+import 'package:appflowy/generated/flowy_svgs.g.dart';
+import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
+import 'package:appflowy/mobile/presentation/widgets/show_flowy_mobile_confirm_dialog.dart';
+import 'package:appflowy/startup/tasks/app_widget.dart';
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
+import 'package:appflowy/workspace/application/recent/recent_views_bloc.dart';
import 'package:appflowy/workspace/application/view/view_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:fluttertoast/fluttertoast.dart';
enum MobileBottomSheetType {
view,
@@ -14,11 +22,13 @@ class MobileViewItemBottomSheet extends StatefulWidget {
const MobileViewItemBottomSheet({
super.key,
required this.view,
+ required this.actions,
this.defaultType = MobileBottomSheetType.view,
});
final ViewPB view;
final MobileBottomSheetType defaultType;
+ final List actions;
@override
State createState() =>
@@ -27,12 +37,14 @@ class MobileViewItemBottomSheet extends StatefulWidget {
class _MobileViewItemBottomSheetState extends State {
MobileBottomSheetType type = MobileBottomSheetType.view;
+ final fToast = FToast();
@override
void initState() {
super.initState();
type = widget.defaultType;
+ fToast.init(AppGlobals.context);
}
@override
@@ -40,6 +52,7 @@ class _MobileViewItemBottomSheetState extends State {
switch (type) {
case MobileBottomSheetType.view:
return MobileViewItemBottomSheetBody(
+ actions: widget.actions,
isFavorite: widget.view.isFavorite,
onAction: (action) {
switch (action) {
@@ -59,7 +72,6 @@ class _MobileViewItemBottomSheetState extends State {
case MobileViewItemBottomSheetBodyAction.delete:
Navigator.pop(context);
context.read().add(const ViewEvent.delete());
-
break;
case MobileViewItemBottomSheetBodyAction.addToFavorites:
case MobileViewItemBottomSheetBodyAction.removeFromFavorites:
@@ -68,6 +80,11 @@ class _MobileViewItemBottomSheetState extends State {
.read()
.add(FavoriteEvent.toggle(widget.view));
break;
+ case MobileViewItemBottomSheetBodyAction.removeFromRecent:
+ _removeFromRecent(context);
+ break;
+ case MobileViewItemBottomSheetBodyAction.divider:
+ break;
}
},
);
@@ -83,4 +100,74 @@ class _MobileViewItemBottomSheetState extends State {
);
}
}
+
+ Future _removeFromRecent(BuildContext context) async {
+ final viewId = context.read().view.id;
+ final recentViewsBloc = context.read();
+ Navigator.pop(context);
+
+ await _showConfirmDialog(
+ onDelete: () {
+ recentViewsBloc.add(RecentViewsEvent.removeRecentViews([viewId]));
+
+ fToast.showToast(
+ child: const _RemoveToast(),
+ positionedToastBuilder: (context, child) {
+ return Positioned.fill(
+ top: 450,
+ child: child,
+ );
+ },
+ );
+ },
+ );
+ }
+
+ Future _showConfirmDialog({required VoidCallback onDelete}) async {
+ await showFlowyCupertinoConfirmDialog(
+ title: LocaleKeys.sideBar_removePageFromRecent.tr(),
+ leftButton: FlowyText.regular(
+ LocaleKeys.button_cancel.tr(),
+ color: const Color(0xFF1456F0),
+ ),
+ rightButton: FlowyText.medium(
+ LocaleKeys.button_delete.tr(),
+ color: const Color(0xFFFE0220),
+ ),
+ onRightButtonPressed: (context) {
+ onDelete();
+ Navigator.pop(context);
+ },
+ );
+ }
+}
+
+class _RemoveToast extends StatelessWidget {
+ const _RemoveToast();
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 13.0),
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(12.0),
+ color: const Color(0xE5171717),
+ ),
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ const FlowySvg(
+ FlowySvgs.success_s,
+ blendMode: null,
+ ),
+ const HSpace(8.0),
+ FlowyText.regular(
+ LocaleKeys.sideBar_removeSuccess.tr(),
+ fontSize: 16.0,
+ color: Colors.white,
+ ),
+ ],
+ ),
+ );
+ }
}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item_body.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item_body.dart
index 624ae33b9fbc1..d9c6382288153 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item_body.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item_body.dart
@@ -1,6 +1,6 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
-import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart';
+import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
@@ -11,6 +11,8 @@ enum MobileViewItemBottomSheetBodyAction {
delete,
addToFavorites,
removeFromFavorites,
+ divider,
+ removeFromRecent,
}
class MobileViewItemBottomSheetBody extends StatelessWidget {
@@ -18,63 +20,124 @@ class MobileViewItemBottomSheetBody extends StatelessWidget {
super.key,
this.isFavorite = false,
required this.onAction,
+ required this.actions,
});
final bool isFavorite;
final void Function(MobileViewItemBottomSheetBodyAction action) onAction;
+ final List actions;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
- MobileQuickActionButton(
+ children:
+ actions.map((action) => _buildActionButton(context, action)).toList(),
+ );
+ }
+
+ Widget _buildActionButton(
+ BuildContext context,
+ MobileViewItemBottomSheetBodyAction action,
+ ) {
+ switch (action) {
+ case MobileViewItemBottomSheetBodyAction.rename:
+ return FlowyOptionTile.text(
text: LocaleKeys.button_rename.tr(),
- icon: FlowySvgs.m_rename_s,
- onTap: () => onAction(
- MobileViewItemBottomSheetBodyAction.rename,
+ leftIcon: const FlowySvg(
+ FlowySvgs.view_item_rename_s,
+ size: Size.square(18),
),
- ),
- _divider(),
- MobileQuickActionButton(
- text: isFavorite
- ? LocaleKeys.button_removeFromFavorites.tr()
- : LocaleKeys.button_addToFavorites.tr(),
- icon: isFavorite
- ? FlowySvgs.m_favorite_selected_lg
- : FlowySvgs.m_favorite_unselected_lg,
- iconColor: isFavorite ? Colors.yellow : null,
+ showTopBorder: false,
+ showBottomBorder: false,
onTap: () => onAction(
- isFavorite
- ? MobileViewItemBottomSheetBodyAction.removeFromFavorites
- : MobileViewItemBottomSheetBodyAction.addToFavorites,
+ MobileViewItemBottomSheetBodyAction.rename,
),
- ),
- _divider(),
- MobileQuickActionButton(
+ );
+ case MobileViewItemBottomSheetBodyAction.duplicate:
+ return FlowyOptionTile.text(
text: LocaleKeys.button_duplicate.tr(),
- icon: FlowySvgs.m_duplicate_s,
+ leftIcon: const FlowySvg(
+ FlowySvgs.duplicate_s,
+ size: Size.square(18),
+ ),
+ showTopBorder: false,
+ showBottomBorder: false,
onTap: () => onAction(
MobileViewItemBottomSheetBodyAction.duplicate,
),
- ),
- _divider(),
- MobileQuickActionButton(
+ );
+
+ case MobileViewItemBottomSheetBodyAction.share:
+ return FlowyOptionTile.text(
+ text: LocaleKeys.button_share.tr(),
+ leftIcon: const FlowySvg(
+ FlowySvgs.share_s,
+ size: Size.square(18),
+ ),
+ showTopBorder: false,
+ showBottomBorder: false,
+ onTap: () => onAction(
+ MobileViewItemBottomSheetBodyAction.share,
+ ),
+ );
+ case MobileViewItemBottomSheetBodyAction.delete:
+ return FlowyOptionTile.text(
text: LocaleKeys.button_delete.tr(),
textColor: Theme.of(context).colorScheme.error,
- icon: FlowySvgs.m_delete_s,
- iconColor: Theme.of(context).colorScheme.error,
+ leftIcon: FlowySvg(
+ FlowySvgs.delete_s,
+ size: const Size.square(18),
+ color: Theme.of(context).colorScheme.error,
+ ),
+ showTopBorder: false,
+ showBottomBorder: false,
onTap: () => onAction(
MobileViewItemBottomSheetBodyAction.delete,
),
- ),
- _divider(),
- ],
- );
- }
+ );
+ case MobileViewItemBottomSheetBodyAction.addToFavorites:
+ return FlowyOptionTile.text(
+ text: LocaleKeys.button_addToFavorites.tr(),
+ leftIcon: const FlowySvg(
+ FlowySvgs.favorite_s,
+ size: Size.square(18),
+ ),
+ showTopBorder: false,
+ showBottomBorder: false,
+ onTap: () => onAction(
+ MobileViewItemBottomSheetBodyAction.addToFavorites,
+ ),
+ );
+ case MobileViewItemBottomSheetBodyAction.removeFromFavorites:
+ return FlowyOptionTile.text(
+ text: LocaleKeys.button_removeFromFavorites.tr(),
+ leftIcon: const FlowySvg(
+ FlowySvgs.favorite_section_remove_from_favorite_s,
+ size: Size.square(18),
+ ),
+ showTopBorder: false,
+ showBottomBorder: false,
+ onTap: () => onAction(
+ MobileViewItemBottomSheetBodyAction.removeFromFavorites,
+ ),
+ );
+ case MobileViewItemBottomSheetBodyAction.removeFromRecent:
+ return FlowyOptionTile.text(
+ text: LocaleKeys.button_removeFromRecent.tr(),
+ leftIcon: const FlowySvg(
+ FlowySvgs.remove_from_recent_s,
+ size: Size.square(18),
+ ),
+ showTopBorder: false,
+ showBottomBorder: false,
+ onTap: () => onAction(
+ MobileViewItemBottomSheetBodyAction.removeFromRecent,
+ ),
+ );
- Widget _divider() => const Divider(
- height: 8.5,
- thickness: 0.5,
- );
+ case MobileViewItemBottomSheetBodyAction.divider:
+ return const Divider(height: 0.5);
+ }
+ }
}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/default_mobile_action_pane.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/default_mobile_action_pane.dart
index f27c5b3b6f585..327db627c4c4f 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/default_mobile_action_pane.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/default_mobile_action_pane.dart
@@ -3,6 +3,7 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/mobile/presentation/page_item/mobile_slide_action_button.dart';
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
import 'package:appflowy/workspace/application/view/view_bloc.dart';
+import 'package:flowy_infra/theme_extension.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
@@ -54,7 +55,7 @@ enum MobilePaneActionType {
context,
showDragHandle: true,
showDivider: false,
- backgroundColor: Theme.of(context).colorScheme.background,
+ backgroundColor: AFThemeExtension.of(context).background,
useRootNavigator: true,
builder: (context) {
return MultiBlocProvider(
@@ -64,8 +65,21 @@ enum MobilePaneActionType {
],
child: BlocBuilder(
builder: (context, state) {
+ final isFavorite = state.view.isFavorite;
return MobileViewItemBottomSheet(
view: viewBloc.state.view,
+ actions: [
+ isFavorite
+ ? MobileViewItemBottomSheetBodyAction
+ .removeFromFavorites
+ : MobileViewItemBottomSheetBodyAction
+ .addToFavorites,
+ MobileViewItemBottomSheetBodyAction.divider,
+ MobileViewItemBottomSheetBodyAction.rename,
+ MobileViewItemBottomSheetBodyAction.duplicate,
+ MobileViewItemBottomSheetBodyAction.divider,
+ MobileViewItemBottomSheetBodyAction.delete,
+ ],
);
},
),
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart
index 718ac5c4e6c15..be815b6550c6a 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart
@@ -1,6 +1,6 @@
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_buttons.dart';
import 'package:appflowy/plugins/base/drag_handler.dart';
-import 'package:flowy_infra_ui/flowy_infra_ui.dart' hide WidgetBuilder;
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
extension BottomSheetPaddingExtension on BuildContext {
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/board/mobile_board_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/board/mobile_board_page.dart
index 7938f474628bb..a1fc2a70a3697 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/database/board/mobile_board_page.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/board/mobile_board_page.dart
@@ -15,6 +15,7 @@ import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:appflowy_board/appflowy_board.dart';
import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -265,7 +266,7 @@ class _BoardContentState extends State<_BoardContent> {
BoxDecoration _makeBoxDecoration(BuildContext context) {
final themeMode = context.read().state.themeMode;
return BoxDecoration(
- color: Theme.of(context).colorScheme.background,
+ color: AFThemeExtension.of(context).background,
borderRadius: const BorderRadius.all(Radius.circular(8)),
border: themeMode == ThemeMode.light
? Border.fromBorderSide(
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/board/widgets/mobile_board_trailing.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/board/widgets/mobile_board_trailing.dart
index 76deaa6f0a6d3..184bd901c15c2 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/database/board/widgets/mobile_board_trailing.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/board/widgets/mobile_board_trailing.dart
@@ -60,7 +60,7 @@ class _MobileBoardTrailingState extends State {
child: IconButton(
icon: Icon(
Icons.close,
- color: style.colorScheme.onBackground,
+ color: style.colorScheme.onSurface,
),
onPressed: () =>
setState(() => _textController.clear()),
@@ -86,7 +86,7 @@ class _MobileBoardTrailingState extends State {
child: Text(
LocaleKeys.button_cancel.tr(),
style: style.textTheme.titleSmall?.copyWith(
- color: style.colorScheme.onBackground,
+ color: style.colorScheme.onSurface,
),
),
onPressed: () => setState(() => isEditing = false),
@@ -96,7 +96,7 @@ class _MobileBoardTrailingState extends State {
LocaleKeys.button_add.tr(),
style: style.textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.bold,
- color: style.colorScheme.onBackground,
+ color: style.colorScheme.onSurface,
),
),
onPressed: () {
@@ -117,14 +117,14 @@ class _MobileBoardTrailingState extends State {
)
: ElevatedButton.icon(
style: ElevatedButton.styleFrom(
- foregroundColor: style.colorScheme.onBackground,
+ foregroundColor: style.colorScheme.onSurface,
backgroundColor: style.colorScheme.secondary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
).copyWith(
overlayColor:
- MaterialStateProperty.all(Theme.of(context).hoverColor),
+ WidgetStateProperty.all(Theme.of(context).hoverColor),
),
icon: const Icon(Icons.add),
label: Text(
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/board/widgets/mobile_hidden_groups_column.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/board/widgets/mobile_hidden_groups_column.dart
index 2e2367b9bbb00..0b0c16f951e36 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/database/board/widgets/mobile_hidden_groups_column.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/board/widgets/mobile_hidden_groups_column.dart
@@ -9,6 +9,7 @@ import 'package:appflowy/plugins/database/widgets/cell/card_cell_skeleton/text_c
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -168,7 +169,7 @@ class MobileHiddenGroup extends StatelessWidget {
return TextButton(
style: TextButton.styleFrom(
textStyle: Theme.of(context).textTheme.bodyMedium,
- foregroundColor: Theme.of(context).colorScheme.onBackground,
+ foregroundColor: AFThemeExtension.of(context).onBackground,
visualDensity: VisualDensity.compact,
),
child: CardCellBuilder(
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 e3cad565abf28..08f6c0b48bd5f 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
@@ -20,6 +20,7 @@ import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'
import 'package:appflowy/plugins/database/widgets/row/row_property.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -131,7 +132,7 @@ class _MobileRowDetailPageState extends State {
void _showCardActions(BuildContext context) {
showMobileBottomSheet(
context,
- backgroundColor: Theme.of(context).colorScheme.background,
+ backgroundColor: AFThemeExtension.of(context).background,
showDragHandle: true,
builder: (_) => Column(
mainAxisSize: MainAxisSize.min,
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 e62ddeb872416..cc6c9b43aac5d 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,17 +22,17 @@ class MobileRowDetailCreateFieldButton extends StatelessWidget {
constraints: const BoxConstraints(minWidth: double.infinity),
child: TextButton.icon(
style: Theme.of(context).textButtonTheme.style?.copyWith(
- shape: MaterialStateProperty.all(
+ shape: WidgetStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0),
),
),
- overlayColor: MaterialStateProperty.all(
+ overlayColor: WidgetStateProperty.all(
Theme.of(context).hoverColor,
),
alignment: AlignmentDirectional.centerStart,
splashFactory: NoSplash.splashFactory,
- padding: const MaterialStatePropertyAll(
+ padding: const WidgetStatePropertyAll(
EdgeInsets.symmetric(vertical: 14, horizontal: 6),
),
),
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_field_bottom_sheets.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_field_bottom_sheets.dart
index b799d339da1ad..7d81801d736b6 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_field_bottom_sheets.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_field_bottom_sheets.dart
@@ -7,6 +7,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.da
import 'package:appflowy/util/field_type_extension.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra/theme_extension.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
@@ -41,7 +42,7 @@ Future showFieldTypeGridBottomSheet(
showCloseButton: true,
elevation: 20,
title: title,
- backgroundColor: Theme.of(context).colorScheme.background,
+ backgroundColor: AFThemeExtension.of(context).background,
enableDraggableScrollable: true,
builder: (context) {
final typeOptionMenuItemValue = mobileSupportedFieldTypes
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_sort_bottom_sheet.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_sort_bottom_sheet.dart
index 909018d1b1a29..8dd224390c3de 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_sort_bottom_sheet.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_sort_bottom_sheet.dart
@@ -438,7 +438,7 @@ class _SortDetailContent extends StatelessWidget {
color: Theme.of(context).colorScheme.surface,
),
splashFactory: NoSplash.splashFactory,
- overlayColor: const MaterialStatePropertyAll(
+ overlayColor: const WidgetStatePropertyAll(
Colors.transparent,
),
onTap: (index) {
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_view_list.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_view_list.dart
index 9ef8ddefb1f05..763da369184ee 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_view_list.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_view_list.dart
@@ -11,6 +11,7 @@ import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -183,7 +184,7 @@ class MobileDatabaseViewListButton extends StatelessWidget {
showMobileBottomSheet(
context,
showDragHandle: true,
- backgroundColor: Theme.of(context).colorScheme.background,
+ backgroundColor: AFThemeExtension.of(context).background,
builder: (_) {
return BlocProvider(
create: (_) =>
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/view/edit_database_view_screen.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/view/edit_database_view_screen.dart
index a2771ece26e75..4d8acbbeba024 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/database/view/edit_database_view_screen.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/view/edit_database_view_screen.dart
@@ -234,7 +234,6 @@ class DatabaseViewSettingTile extends StatelessWidget {
showHeader: true,
showBackButton: true,
title: LocaleKeys.grid_settings_properties.tr(),
- showDivider: true,
builder: (_) {
return BlocProvider.value(
value: context.read(),
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/favorite/mobile_favorite_folder.dart b/frontend/appflowy_flutter/lib/mobile/presentation/favorite/mobile_favorite_folder.dart
index f9fcba5754209..ca012891f6066 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/favorite/mobile_favorite_folder.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/favorite/mobile_favorite_folder.dart
@@ -1,5 +1,3 @@
-import 'package:flutter/material.dart';
-
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/mobile/presentation/home/favorite_folder/mobile_home_favorite_folder.dart';
@@ -10,6 +8,7 @@ import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
@@ -17,14 +16,15 @@ class MobileFavoritePageFolder extends StatelessWidget {
const MobileFavoritePageFolder({
super.key,
required this.userProfile,
- required this.workspaceId,
});
final UserProfilePB userProfile;
- final String workspaceId;
@override
Widget build(BuildContext context) {
+ final workspaceId =
+ context.read().state.currentWorkspace?.workspaceId ??
+ '';
return MultiBlocProvider(
providers: [
BlocProvider(
@@ -67,7 +67,8 @@ class MobileFavoritePageFolder extends StatelessWidget {
MobileFavoriteFolder(
showHeader: false,
forceExpanded: true,
- views: favoriteState.views,
+ views:
+ favoriteState.views.map((e) => e.item).toList(),
),
const VSpace(100.0),
],
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/favorite/mobile_favorite_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/favorite/mobile_favorite_page.dart
index 7afc740b45d9d..e6d2d895b1461 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/favorite/mobile_favorite_page.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/favorite/mobile_favorite_page.dart
@@ -64,8 +64,6 @@ class MobileFavoriteScreen extends StatelessWidget {
builder: (context, state) {
return MobileFavoritePage(
userProfile: userProfile,
- workspaceId: state.currentWorkspace?.workspaceId ??
- workspaceSetting.workspaceId,
);
},
),
@@ -81,11 +79,9 @@ class MobileFavoritePage extends StatelessWidget {
const MobileFavoritePage({
super.key,
required this.userProfile,
- required this.workspaceId,
});
final UserProfilePB userProfile;
- final String workspaceId;
@override
Widget build(BuildContext context) {
@@ -108,7 +104,6 @@ class MobileFavoritePage extends StatelessWidget {
Expanded(
child: MobileFavoritePageFolder(
userProfile: userProfile,
- workspaceId: workspaceId,
),
),
],
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
new file mode 100644
index 0000000000000..684442a74a7e5
--- /dev/null
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/favorite_folder/favorite_space.dart
@@ -0,0 +1,126 @@
+import 'package:appflowy/mobile/application/mobile_router.dart';
+import 'package:appflowy/mobile/presentation/home/shared/empty_placeholder.dart';
+import 'package:appflowy/mobile/presentation/home/shared/mobile_view_card.dart';
+import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
+import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
+import 'package:appflowy/workspace/application/user/prelude.dart';
+import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
+import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+class MobileFavoriteSpace extends StatefulWidget {
+ const MobileFavoriteSpace({
+ super.key,
+ required this.userProfile,
+ });
+
+ final UserProfilePB userProfile;
+
+ @override
+ State createState() => _MobileFavoriteSpaceState();
+}
+
+class _MobileFavoriteSpaceState extends State
+ with AutomaticKeepAliveClientMixin {
+ @override
+ bool get wantKeepAlive => true;
+
+ @override
+ Widget build(BuildContext context) {
+ super.build(context);
+ final workspaceId =
+ context.read().state.currentWorkspace?.workspaceId ??
+ '';
+ return MultiBlocProvider(
+ providers: [
+ BlocProvider(
+ create: (_) => SidebarSectionsBloc()
+ ..add(
+ SidebarSectionsEvent.initial(widget.userProfile, workspaceId),
+ ),
+ ),
+ BlocProvider(
+ create: (_) => FavoriteBloc()..add(const FavoriteEvent.initial()),
+ ),
+ ],
+ child: BlocListener(
+ listener: (context, state) =>
+ context.read().add(const FavoriteEvent.initial()),
+ child: MultiBlocListener(
+ listeners: [
+ BlocListener(
+ listenWhen: (p, c) =>
+ p.lastCreatedRootView?.id != c.lastCreatedRootView?.id,
+ listener: (context, state) =>
+ context.pushView(state.lastCreatedRootView!),
+ ),
+ ],
+ child: Builder(
+ builder: (context) {
+ final favoriteState = context.watch().state;
+
+ if (favoriteState.isLoading) {
+ return const SizedBox.shrink();
+ }
+
+ if (favoriteState.views.isEmpty) {
+ return const EmptySpacePlaceholder(
+ type: MobileViewCardType.favorite,
+ );
+ }
+
+ return _FavoriteViews(
+ favoriteViews: favoriteState.views.reversed.toList(),
+ );
+ },
+ ),
+ ),
+ ),
+ );
+ }
+}
+
+class _FavoriteViews extends StatelessWidget {
+ const _FavoriteViews({
+ required this.favoriteViews,
+ });
+
+ final List favoriteViews;
+
+ @override
+ Widget build(BuildContext context) {
+ return Scrollbar(
+ child: ListView.separated(
+ key: const PageStorageKey('favorite_views_page_storage_key'),
+ padding: const EdgeInsets.symmetric(
+ horizontal: HomeSpaceViewSizes.mHorizontalPadding,
+ ),
+ itemBuilder: (context, index) {
+ final view = favoriteViews[index];
+ return Container(
+ padding: const EdgeInsets.symmetric(vertical: 24.0),
+ decoration: BoxDecoration(
+ border: Border(
+ bottom: BorderSide(
+ color: Theme.of(context).dividerColor,
+ width: 0.5,
+ ),
+ ),
+ ),
+ child: MobileViewCard(
+ key: ValueKey(view.item.id),
+ view: view.item,
+ timestamp: view.timestamp,
+ type: MobileViewCardType.favorite,
+ ),
+ );
+ },
+ separatorBuilder: (context, index) => const HSpace(8),
+ itemCount: favoriteViews.length,
+ ),
+ );
+ }
+}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/favorite_folder/mobile_home_favorite_folder.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/favorite_folder/mobile_home_favorite_folder.dart
index c56d369676d4b..d683cf3507bc7 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/home/favorite_folder/mobile_home_favorite_folder.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/favorite_folder/mobile_home_favorite_folder.dart
@@ -1,5 +1,3 @@
-import 'package:flutter/material.dart';
-
import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/default_mobile_action_pane.dart';
import 'package:appflowy/mobile/presentation/home/favorite_folder/mobile_home_favorite_folder_header.dart';
@@ -7,6 +5,7 @@ import 'package:appflowy/mobile/presentation/page_item/mobile_view_item.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class MobileFavoriteFolder extends StatelessWidget {
@@ -28,7 +27,7 @@ class MobileFavoriteFolder extends StatelessWidget {
}
return BlocProvider(
- create: (context) => FolderBloc(type: FolderCategoryType.favorite)
+ create: (context) => FolderBloc(type: FolderSpaceType.favorite)
..add(
const FolderEvent.initial(),
),
@@ -55,9 +54,9 @@ class MobileFavoriteFolder extends StatelessWidget {
...views.map(
(view) => MobileViewItem(
key: ValueKey(
- '${FolderCategoryType.favorite.name} ${view.id}',
+ '${FolderSpaceType.favorite.name} ${view.id}',
),
- categoryType: FolderCategoryType.favorite,
+ spaceType: FolderSpaceType.favorite,
isDraggable: false,
isFirstChild: view.id == views.first.id,
isFeedback: false,
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
new file mode 100644
index 0000000000000..6ba27a6766128
--- /dev/null
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/home_space/home_space.dart
@@ -0,0 +1,44 @@
+import 'package:appflowy/mobile/presentation/home/mobile_folders.dart';
+import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
+import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
+import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+class MobileHomeSpace extends StatefulWidget {
+ const MobileHomeSpace({super.key, required this.userProfile});
+
+ final UserProfilePB userProfile;
+
+ @override
+ State createState() => _MobileHomeSpaceState();
+}
+
+class _MobileHomeSpaceState extends State
+ with AutomaticKeepAliveClientMixin {
+ @override
+ bool get wantKeepAlive => true;
+
+ @override
+ Widget build(BuildContext context) {
+ super.build(context);
+ final workspaceId =
+ context.read().state.currentWorkspace?.workspaceId ??
+ '';
+ return Scrollbar(
+ child: SingleChildScrollView(
+ child: Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: HomeSpaceViewSizes.mHorizontalPadding,
+ vertical: HomeSpaceViewSizes.mVerticalPadding,
+ ),
+ 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 7631383faabea..100649701d93f 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_folders.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_folders.dart
@@ -1,5 +1,7 @@
+import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/mobile_router.dart';
+import 'package:appflowy/mobile/presentation/home/home.dart';
import 'package:appflowy/mobile/presentation/home/section_folder/mobile_home_section_folder.dart';
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
@@ -11,6 +13,7 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
+import 'package:go_router/go_router.dart';
// Contains Public And Private Sections
class MobileFolders extends StatelessWidget {
@@ -70,24 +73,25 @@ class MobileFolders extends StatelessWidget {
? [
MobileSectionFolder(
title: LocaleKeys.sideBar_workspace.tr(),
- categoryType: FolderCategoryType.public,
+ spaceType: FolderSpaceType.public,
views: state.section.publicViews,
),
const VSpace(8.0),
MobileSectionFolder(
title: LocaleKeys.sideBar_private.tr(),
- categoryType: FolderCategoryType.private,
+ spaceType: FolderSpaceType.private,
views: state.section.privateViews,
),
]
: [
MobileSectionFolder(
title: LocaleKeys.sideBar_personal.tr(),
- categoryType: FolderCategoryType.public,
+ spaceType: FolderSpaceType.public,
views: state.section.publicViews,
),
],
- const VSpace(8.0),
+ const VSpace(4.0),
+ const _TrashButton(),
],
),
);
@@ -97,3 +101,28 @@ class MobileFolders extends StatelessWidget {
);
}
}
+
+class _TrashButton extends StatelessWidget {
+ const _TrashButton();
+
+ @override
+ Widget build(BuildContext context) {
+ return SizedBox(
+ height: 52,
+ child: FlowyButton(
+ expand: true,
+ margin: const EdgeInsets.symmetric(vertical: 8),
+ leftIcon: const FlowySvg(
+ FlowySvgs.m_delete_s,
+ ),
+ leftIconSize: const Size.square(18),
+ iconPadding: 10.0,
+ text: FlowyText.regular(
+ LocaleKeys.trash_text.tr(),
+ fontSize: 16.0,
+ ),
+ onTap: () => context.push(MobileHomeTrashPage.routeName),
+ ),
+ );
+ }
+}
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 69759fc508b63..e0b7c626d91a9 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,23 +1,20 @@
import 'dart:io';
-import 'package:appflowy/generated/flowy_svgs.g.dart';
-import 'package:appflowy/generated/locale_keys.g.dart';
-import 'package:appflowy/mobile/presentation/home/home.dart';
-import 'package:appflowy/mobile/presentation/home/mobile_folders.dart';
import 'package:appflowy/mobile/presentation/home/mobile_home_page_header.dart';
-import 'package:appflowy/mobile/presentation/home/recent_folder/mobile_home_recent_views.dart';
+import 'package:appflowy/mobile/presentation/home/tab/mobile_space_tab.dart';
+import 'package:appflowy/mobile/presentation/home/tab/space_order_bloc.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
+import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
+import 'package:appflowy/workspace/application/recent/cached_recent_service.dart';
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
import 'package:appflowy/workspace/presentation/home/errors/workspace_failed_screen.dart';
+import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
-import 'package:easy_localization/easy_localization.dart';
-import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
class MobileHomeScreen extends StatelessWidget {
@@ -84,66 +81,49 @@ class MobileHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return BlocProvider(
- create: (_) => UserWorkspaceBloc(userProfile: userProfile)
- ..add(
- const UserWorkspaceEvent.initial(),
+ return MultiBlocProvider(
+ providers: [
+ BlocProvider(
+ create: (_) => UserWorkspaceBloc(userProfile: userProfile)
+ ..add(
+ const UserWorkspaceEvent.initial(),
+ ),
+ ),
+ BlocProvider(
+ create: (context) =>
+ FavoriteBloc()..add(const FavoriteEvent.initial()),
),
- child: BlocBuilder(
+ ],
+ child: BlocConsumer(
buildWhen: (previous, current) =>
previous.currentWorkspace?.workspaceId !=
current.currentWorkspace?.workspaceId,
+ listener: (context, state) => getIt().reset(),
builder: (context, state) {
if (state.currentWorkspace == null) {
return const SizedBox.shrink();
}
+
return Column(
children: [
// Header
Padding(
padding: EdgeInsets.only(
- left: 16,
- right: 16,
+ left: HomeSpaceViewSizes.mHorizontalPadding,
+ right: 8.0,
top: Platform.isAndroid ? 8.0 : 0.0,
),
child: MobileHomePageHeader(
userProfile: userProfile,
),
),
- const Divider(),
- // Folder
Expanded(
- child: Scrollbar(
- child: SingleChildScrollView(
- child: Padding(
- padding: const EdgeInsets.symmetric(vertical: 8.0),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- mainAxisSize: MainAxisSize.min,
- children: [
- // Recent files
- const MobileRecentFolder(),
-
- // Folders
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 24),
- child: MobileFolders(
- user: userProfile,
- workspaceId:
- state.currentWorkspace?.workspaceId ??
- workspaceSetting.workspaceId,
- showFavorite: false,
- ),
- ),
- const SizedBox(height: 8),
- const Padding(
- padding: EdgeInsets.symmetric(horizontal: 24),
- child: _TrashButton(),
- ),
- ],
- ),
- ),
+ child: BlocProvider(
+ create: (context) =>
+ SpaceOrderBloc()..add(const SpaceOrderEvent.initial()),
+ child: MobileSpaceTab(
+ userProfile: userProfile,
),
),
),
@@ -154,25 +134,3 @@ class MobileHomePage extends StatelessWidget {
);
}
}
-
-class _TrashButton extends StatelessWidget {
- const _TrashButton();
-
- @override
- Widget build(BuildContext context) {
- return FlowyButton(
- expand: true,
- margin: const EdgeInsets.symmetric(vertical: 8),
- leftIcon: FlowySvg(
- FlowySvgs.m_delete_m,
- color: Theme.of(context).colorScheme.onSurface,
- ),
- leftIconSize: const Size.square(24),
- text: FlowyText.medium(
- LocaleKeys.trash_text.tr(),
- fontSize: 18.0,
- ),
- onTap: () => context.push(MobileHomeTrashPage.routeName),
- );
- }
-}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page_header.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page_header.dart
index 1759d32aaff5c..5ae2ff1430aed 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page_header.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page_header.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/mobile/presentation/bottom_sheet/bottom_sheet.dart';
@@ -15,6 +13,7 @@ import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sid
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
@@ -36,7 +35,7 @@ class MobileHomePageHeader extends StatelessWidget {
final isCollaborativeWorkspace =
context.read().state.isCollabWorkspaceOn;
return ConstrainedBox(
- constraints: const BoxConstraints(minHeight: 52),
+ constraints: const BoxConstraints(minHeight: 56),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
@@ -45,11 +44,14 @@ class MobileHomePageHeader extends StatelessWidget {
? _MobileWorkspace(userProfile: userProfile)
: _MobileUser(userProfile: userProfile),
),
- IconButton(
- onPressed: () => context.push(
+ GestureDetector(
+ onTap: () => context.push(
MobileHomeSettingPage.routeName,
),
- icon: const FlowySvg(FlowySvgs.m_setting_m),
+ child: const Padding(
+ padding: EdgeInsets.all(8.0),
+ child: FlowySvg(FlowySvgs.m_setting_m),
+ ),
),
],
),
@@ -120,12 +122,12 @@ class _MobileWorkspace extends StatelessWidget {
},
child: Row(
children: [
- const HSpace(2.0),
SizedBox.square(
dimension: 34.0,
child: WorkspaceIcon(
workspace: currentWorkspace,
iconSize: 26,
+ fontSize: 16.0,
enableEdit: false,
onSelected: (result) => context.read().add(
UserWorkspaceEvent.updateWorkspaceIcon(
@@ -142,7 +144,7 @@ class _MobileWorkspace extends StatelessWidget {
children: [
Row(
children: [
- FlowyText.medium(
+ FlowyText.semibold(
currentWorkspace.name,
fontSize: 16.0,
overflow: TextOverflow.ellipsis,
@@ -151,7 +153,7 @@ class _MobileWorkspace extends StatelessWidget {
const FlowySvg(FlowySvgs.list_dropdown_s),
],
),
- FlowyText.medium(
+ FlowyText.regular(
userProfile.email.isNotEmpty
? userProfile.email
: userProfile.name,
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_trash_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_trash_page.dart
index a9c2f1b9331fb..2ee9e14175685 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_trash_page.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_trash_page.dart
@@ -178,7 +178,7 @@ class _DeletedFilesListView extends StatelessWidget {
title: Text(
deletedFile.name,
style: theme.textTheme.labelMedium
- ?.copyWith(color: theme.colorScheme.onBackground),
+ ?.copyWith(color: theme.colorScheme.onSurface),
),
horizontalTitleGap: 0,
tileColor: theme.colorScheme.onSurface.withOpacity(0.1),
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/recent_folder/mobile_home_recent_views.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/recent_folder/mobile_home_recent_views.dart
index 4dc9f28155b9a..a2b9ae52c780c 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/home/recent_folder/mobile_home_recent_views.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/recent_folder/mobile_home_recent_views.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/mobile/presentation/bottom_sheet/bottom_sheet.dart';
@@ -9,7 +7,9 @@ import 'package:appflowy/workspace/application/recent/prelude.dart';
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
@@ -38,7 +38,8 @@ class _MobileRecentFolderState extends State {
builder: (context, state) {
final ids = {};
- List recentViews = state.views.reversed.toList();
+ List recentViews =
+ state.views.reversed.map((e) => e.item).toList();
recentViews.retainWhere((element) => ids.add(element.id));
// only keep the first 20 items.
@@ -91,7 +92,7 @@ class _RecentViews extends StatelessWidget {
context,
showDivider: false,
showDragHandle: true,
- backgroundColor: Theme.of(context).colorScheme.background,
+ backgroundColor: AFThemeExtension.of(context).background,
builder: (_) {
return Column(
children: [
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
new file mode 100644
index 0000000000000..3cab831c23400
--- /dev/null
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/recent_folder/recent_space.dart
@@ -0,0 +1,96 @@
+import 'package:appflowy/mobile/presentation/home/shared/empty_placeholder.dart';
+import 'package:appflowy/mobile/presentation/home/shared/mobile_view_card.dart';
+import 'package:appflowy/workspace/application/recent/prelude.dart';
+import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
+import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+class MobileRecentSpace extends StatefulWidget {
+ const MobileRecentSpace({super.key});
+
+ @override
+ State createState() => _MobileRecentSpaceState();
+}
+
+class _MobileRecentSpaceState extends State
+ with AutomaticKeepAliveClientMixin {
+ @override
+ bool get wantKeepAlive => true;
+
+ @override
+ Widget build(BuildContext context) {
+ super.build(context);
+ return BlocProvider(
+ create: (context) =>
+ RecentViewsBloc()..add(const RecentViewsEvent.initial()),
+ child: BlocBuilder(
+ builder: (context, state) {
+ if (state.isLoading) {
+ return const SizedBox.shrink();
+ }
+
+ final recentViews = _filterRecentViews(state.views);
+
+ if (recentViews.isEmpty) {
+ return const Center(
+ child: EmptySpacePlaceholder(type: MobileViewCardType.recent),
+ );
+ }
+
+ return _RecentViews(recentViews: recentViews);
+ },
+ ),
+ );
+ }
+
+ List _filterRecentViews(List recentViews) {
+ final ids = {};
+ final filteredRecentViews = recentViews.reversed.toList();
+ filteredRecentViews.retainWhere((e) => ids.add(e.item.id));
+ return filteredRecentViews;
+ }
+}
+
+class _RecentViews extends StatelessWidget {
+ const _RecentViews({
+ required this.recentViews,
+ });
+
+ final List recentViews;
+
+ @override
+ Widget build(BuildContext context) {
+ return Scrollbar(
+ child: ListView.separated(
+ key: const PageStorageKey('recent_views_page_storage_key'),
+ padding: const EdgeInsets.symmetric(
+ horizontal: HomeSpaceViewSizes.mHorizontalPadding,
+ ),
+ itemBuilder: (context, index) {
+ final sectionView = recentViews[index];
+ return Container(
+ padding: const EdgeInsets.symmetric(vertical: 24.0),
+ decoration: BoxDecoration(
+ border: Border(
+ bottom: BorderSide(
+ color: Theme.of(context).dividerColor,
+ width: 0.5,
+ ),
+ ),
+ ),
+ child: MobileViewCard(
+ key: ValueKey(sectionView.item.id),
+ view: sectionView.item,
+ timestamp: sectionView.timestamp,
+ type: MobileViewCardType.recent,
+ ),
+ );
+ },
+ separatorBuilder: (context, index) => const HSpace(8),
+ itemCount: recentViews.length,
+ ),
+ );
+ }
+}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/section_folder/mobile_home_section_folder.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/section_folder/mobile_home_section_folder.dart
index c9ea1453c99f5..7a14a4d48d158 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/home/section_folder/mobile_home_section_folder.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/section_folder/mobile_home_section_folder.dart
@@ -1,16 +1,13 @@
-import 'package:flutter/material.dart';
-
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/mobile_router.dart';
-import 'package:appflowy/mobile/presentation/bottom_sheet/default_mobile_action_pane.dart';
import 'package:appflowy/mobile/presentation/home/section_folder/mobile_home_section_folder_header.dart';
import 'package:appflowy/mobile/presentation/page_item/mobile_view_item.dart';
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
-import 'package:appflowy/workspace/application/view/view_bloc.dart';
+import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
-import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class MobileSectionFolder extends StatelessWidget {
@@ -18,17 +15,17 @@ class MobileSectionFolder extends StatelessWidget {
super.key,
required this.title,
required this.views,
- required this.categoryType,
+ required this.spaceType,
});
final String title;
final List views;
- final FolderCategoryType categoryType;
+ final FolderSpaceType spaceType;
@override
Widget build(BuildContext context) {
return BlocProvider(
- create: (context) => FolderBloc(type: categoryType)
+ create: (context) => FolderBloc(type: spaceType)
..add(
const FolderEvent.initial(),
),
@@ -36,53 +33,42 @@ class MobileSectionFolder extends StatelessWidget {
builder: (context, state) {
return Column(
children: [
- MobileSectionFolderHeader(
- title: title,
- isExpanded: context.read().state.isExpanded,
- onPressed: () => context
- .read()
- .add(const FolderEvent.expandOrUnExpand()),
- onAdded: () {
- context.read().add(
- SidebarSectionsEvent.createRootViewInSection(
- name:
- LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
- index: 0,
- viewSection: categoryType.toViewSectionPB,
- ),
- );
- context.read().add(
- const FolderEvent.expandOrUnExpand(isExpanded: true),
- );
- },
- ),
- const VSpace(8.0),
- const Divider(
- height: 1,
+ SizedBox(
+ height: HomeSpaceViewSizes.mViewHeight,
+ child: MobileSectionFolderHeader(
+ title: title,
+ isExpanded: context.read().state.isExpanded,
+ onPressed: () => context
+ .read()
+ .add(const FolderEvent.expandOrUnExpand()),
+ onAdded: () {
+ context.read().add(
+ SidebarSectionsEvent.createRootViewInSection(
+ name: LocaleKeys.menuAppHeader_defaultNewPageName
+ .tr(),
+ index: 0,
+ viewSection: spaceType.toViewSectionPB,
+ ),
+ );
+ context.read().add(
+ const FolderEvent.expandOrUnExpand(isExpanded: true),
+ );
+ },
+ ),
),
if (state.isExpanded)
...views.map(
(view) => MobileViewItem(
key: ValueKey(
- '${FolderCategoryType.private.name} ${view.id}',
+ '${FolderSpaceType.private.name} ${view.id}',
),
- categoryType: categoryType,
+ spaceType: spaceType,
isFirstChild: view.id == views.first.id,
view: view,
level: 0,
- leftPadding: 16,
+ leftPadding: HomeSpaceViewSizes.leftPadding,
isFeedback: false,
onSelected: context.pushView,
- endActionPane: (context) {
- final view = context.read().state.view;
- return buildEndActionPane(context, [
- MobilePaneActionType.delete,
- view.isFavorite
- ? MobilePaneActionType.removeFromFavorites
- : MobilePaneActionType.addToFavorites,
- MobilePaneActionType.more,
- ]);
- },
),
),
],
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/section_folder/mobile_home_section_folder_header.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/section_folder/mobile_home_section_folder_header.dart
index 3ba15df25d56a..49a9829d8a03b 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/home/section_folder/mobile_home_section_folder_header.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/section_folder/mobile_home_section_folder_header.dart
@@ -1,4 +1,5 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
+import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
@@ -29,24 +30,23 @@ class _MobileSectionFolderHeaderState extends State {
@override
Widget build(BuildContext context) {
- const iconSize = 32.0;
return Row(
children: [
Expanded(
child: FlowyButton(
- text: FlowyText.semibold(
+ text: FlowyText.medium(
widget.title,
- fontSize: 20.0,
+ fontSize: 16.0,
),
margin: const EdgeInsets.symmetric(vertical: 8),
expandText: false,
+ iconPadding: 2,
mainAxisAlignment: MainAxisAlignment.start,
rightIcon: AnimatedRotation(
duration: const Duration(milliseconds: 200),
turns: _turns,
- child: const Icon(
- Icons.keyboard_arrow_down_rounded,
- color: Colors.grey,
+ child: const FlowySvg(
+ FlowySvgs.m_spaces_expand_s,
),
),
onTap: () {
@@ -60,12 +60,10 @@ class _MobileSectionFolderHeaderState extends State {
FlowyIconButton(
key: mobileCreateNewPageButtonKey,
hoverColor: Theme.of(context).colorScheme.secondaryContainer,
- iconPadding: const EdgeInsets.all(2),
- height: iconSize,
- width: iconSize,
+ height: HomeSpaceViewSizes.mViewButtonDimension,
+ width: HomeSpaceViewSizes.mViewButtonDimension,
icon: const FlowySvg(
- FlowySvgs.add_s,
- size: Size.square(iconSize),
+ FlowySvgs.m_space_add_s,
),
onPressed: widget.onAdded,
),
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/empty_placeholder.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/empty_placeholder.dart
new file mode 100644
index 0000000000000..e59fa87538b90
--- /dev/null
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/empty_placeholder.dart
@@ -0,0 +1,55 @@
+import 'package:appflowy/generated/flowy_svgs.g.dart';
+import 'package:appflowy/generated/locale_keys.g.dart';
+import 'package:appflowy/mobile/presentation/home/shared/mobile_view_card.dart';
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flutter/material.dart';
+
+class EmptySpacePlaceholder extends StatelessWidget {
+ const EmptySpacePlaceholder({super.key, required this.type});
+
+ final MobileViewCardType type;
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 48.0),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ const FlowySvg(
+ FlowySvgs.m_empty_page_xl,
+ ),
+ const VSpace(16.0),
+ FlowyText.medium(
+ _emptyPageText,
+ fontSize: 18.0,
+ textAlign: TextAlign.center,
+ ),
+ const VSpace(8.0),
+ FlowyText.regular(
+ _emptyPageSubText,
+ fontSize: 17.0,
+ maxLines: 10,
+ textAlign: TextAlign.center,
+ lineHeight: 1.3,
+ color: Theme.of(context).hintColor,
+ ),
+ ],
+ ),
+ );
+ }
+
+ String get _emptyPageText => switch (type) {
+ MobileViewCardType.recent => LocaleKeys.sideBar_emptyRecent.tr(),
+ MobileViewCardType.favorite => LocaleKeys.sideBar_emptyFavorite.tr(),
+ };
+
+ String get _emptyPageSubText => switch (type) {
+ MobileViewCardType.recent =>
+ LocaleKeys.sideBar_emptyRecentDescription.tr(),
+ MobileViewCardType.favorite =>
+ LocaleKeys.sideBar_emptyFavoriteDescription.tr(),
+ };
+}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/mobile_view_card.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/mobile_view_card.dart
new file mode 100644
index 0000000000000..c4498a27cdf63
--- /dev/null
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/mobile_view_card.dart
@@ -0,0 +1,399 @@
+import 'dart:io';
+
+import 'package:appflowy/generated/flowy_svgs.g.dart';
+import 'package:appflowy/generated/locale_keys.g.dart';
+import 'package:appflowy/mobile/application/mobile_router.dart';
+import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
+import 'package:appflowy/mobile/application/recent/recent_view_bloc.dart';
+import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
+import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
+import 'package:appflowy/shared/appflowy_network_image.dart';
+import 'package:appflowy/shared/flowy_gradient_colors.dart';
+import 'package:appflowy/util/string_extension.dart';
+import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
+import 'package:appflowy/workspace/application/recent/recent_views_bloc.dart';
+import 'package:appflowy/workspace/application/view/view_bloc.dart';
+import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
+import 'package:appflowy_editor/appflowy_editor.dart';
+import 'package:easy_localization/easy_localization.dart';
+import 'package:fixnum/fixnum.dart';
+import 'package:flowy_infra/theme_extension.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:google_fonts/google_fonts.dart';
+import 'package:provider/provider.dart';
+import 'package:string_validator/string_validator.dart';
+
+enum MobileViewCardType {
+ recent,
+ favorite;
+
+ String get lastOperationHintText => switch (this) {
+ MobileViewCardType.recent => LocaleKeys.sideBar_lastViewed.tr(),
+ MobileViewCardType.favorite => LocaleKeys.sideBar_favoriteAt.tr(),
+ };
+}
+
+class MobileViewCard extends StatelessWidget {
+ const MobileViewCard({
+ super.key,
+ required this.view,
+ this.timestamp,
+ required this.type,
+ });
+
+ final ViewPB view;
+ final Int64? timestamp;
+ final MobileViewCardType type;
+
+ @override
+ Widget build(BuildContext context) {
+ return MultiBlocProvider(
+ providers: [
+ BlocProvider(
+ create: (context) => ViewBloc(view: view, shouldLoadChildViews: false)
+ ..add(const ViewEvent.initial()),
+ ),
+ BlocProvider(
+ create: (context) =>
+ RecentViewBloc(view: view)..add(const RecentViewEvent.initial()),
+ ),
+ ],
+ child: BlocBuilder(
+ builder: (context, state) {
+ return GestureDetector(
+ behavior: HitTestBehavior.opaque,
+ onTapUp: (_) => context.pushView(view),
+ onLongPressUp: () => _showActionSheet(context),
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Expanded(child: _buildDescription(context, state)),
+ const HSpace(20.0),
+ SizedBox(
+ width: 84,
+ height: 60,
+ child: _buildCover(context, state),
+ ),
+ ],
+ ),
+ );
+ },
+ ),
+ );
+ }
+
+ Widget _buildDescription(BuildContext context, RecentViewState state) {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ // page icon & page title
+ _buildTitle(context, state),
+ const VSpace(12.0),
+ // author & last viewed
+ _buildNameAndLastViewed(context, state),
+ ],
+ );
+ }
+
+ Widget _buildNameAndLastViewed(BuildContext context, RecentViewState state) {
+ final supportAvatar = isURL(state.icon);
+ if (!supportAvatar) {
+ return _buildLastViewed(context);
+ }
+ return Row(
+ children: [
+ _buildAvatar(context, state),
+ Flexible(child: _buildAuthor(context, state)),
+ const Padding(
+ padding: EdgeInsets.symmetric(horizontal: 3.0),
+ child: FlowySvg(FlowySvgs.dot_s),
+ ),
+ _buildLastViewed(context),
+ ],
+ );
+ }
+
+ Widget _buildAvatar(BuildContext context, RecentViewState state) {
+ final userProfile = Provider.of(context);
+ final iconUrl = userProfile?.iconUrl;
+ if (iconUrl == null ||
+ iconUrl.isEmpty ||
+ view.createdBy != userProfile?.id) {
+ return const SizedBox.shrink();
+ }
+
+ return Padding(
+ padding: const EdgeInsets.only(top: 2, bottom: 2, right: 8),
+ child: ClipRRect(
+ borderRadius: BorderRadius.circular(8.0),
+ child: SizedBox.square(
+ dimension: 16.0,
+ child: FlowyNetworkImage(
+ url: iconUrl,
+ ),
+ ),
+ ),
+ );
+ }
+
+ Widget _buildCover(BuildContext context, RecentViewState state) {
+ return ClipRRect(
+ borderRadius: BorderRadius.circular(8),
+ child: _ViewCover(
+ coverTypeV1: state.coverTypeV1,
+ coverTypeV2: state.coverTypeV2,
+ value: state.coverValue,
+ ),
+ );
+ }
+
+ Widget _buildTitle(BuildContext context, RecentViewState state) {
+ final name = state.name;
+ final icon = state.icon;
+ final fontFamily = Platform.isAndroid || Platform.isLinux
+ ? GoogleFonts.notoColorEmoji().fontFamily
+ : null;
+ return RichText(
+ maxLines: 3,
+ overflow: TextOverflow.ellipsis,
+ text: TextSpan(
+ children: [
+ TextSpan(
+ text: icon,
+ style: Theme.of(context).textTheme.bodyMedium!.copyWith(
+ fontSize: 17.0,
+ fontWeight: FontWeight.w600,
+ fontFamily: fontFamily,
+ ),
+ ),
+ if (icon.isNotEmpty) const WidgetSpan(child: HSpace(2.0)),
+ TextSpan(
+ text: name,
+ style: Theme.of(context).textTheme.bodyMedium!.copyWith(
+ fontSize: 16.0,
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+
+ Widget _buildAuthor(BuildContext context, RecentViewState state) {
+ return FlowyText.regular(
+ // view.createdBy.toString(),
+ 'Lucas',
+ fontSize: 12.0,
+ color: Theme.of(context).hintColor,
+ overflow: TextOverflow.ellipsis,
+ );
+ }
+
+ Widget _buildLastViewed(BuildContext context) {
+ if (timestamp == null) {
+ return const SizedBox.shrink();
+ }
+ final date = _formatTimestamp(
+ timestamp!.toInt() * 1000,
+ );
+ return FlowyText.regular(
+ date,
+ fontSize: 12.0,
+ color: Theme.of(context).hintColor,
+ );
+ }
+
+ String _formatTimestamp(int timestamp) {
+ final now = DateTime.now();
+ final dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp);
+ final difference = now.difference(dateTime);
+ final String date;
+
+ if (difference.inMinutes < 1) {
+ date = LocaleKeys.sideBar_justNow.tr();
+ } else if (difference.inHours < 1) {
+ // Less than 1 hour
+ date = LocaleKeys.sideBar_minutesAgo
+ .tr(namedArgs: {'count': difference.inMinutes.toString()});
+ } else if (difference.inHours >= 1 && difference.inHours < 24) {
+ // Between 1 hour and 24 hours
+ date = DateFormat('h:mm a').format(dateTime);
+ } else if (difference.inDays >= 1 && dateTime.year == now.year) {
+ // More than 24 hours but within the current year
+ date = DateFormat('M/d, h:mm a').format(dateTime);
+ } else {
+ // Other cases (previous years)
+ date = DateFormat('M/d/yyyy, h:mm a').format(dateTime);
+ }
+
+ if (difference.inHours >= 1) {
+ return '${type.lastOperationHintText} $date';
+ }
+
+ return date;
+ }
+
+ Future _showActionSheet(BuildContext context) async {
+ final viewBloc = context.read();
+ final favoriteBloc = context.read();
+ final recentViewsBloc = context.read();
+ await showMobileBottomSheet(
+ context,
+ showDragHandle: true,
+ showDivider: false,
+ backgroundColor: AFThemeExtension.of(context).background,
+ useRootNavigator: true,
+ builder: (context) {
+ return MultiBlocProvider(
+ providers: [
+ BlocProvider.value(value: viewBloc),
+ BlocProvider.value(value: favoriteBloc),
+ if (recentViewsBloc != null)
+ BlocProvider.value(value: recentViewsBloc),
+ ],
+ child: BlocBuilder(
+ builder: (context, state) {
+ final isFavorite = state.view.isFavorite;
+ return MobileViewItemBottomSheet(
+ view: viewBloc.state.view,
+ actions: _buildActions(isFavorite),
+ );
+ },
+ ),
+ );
+ },
+ );
+ }
+
+ List _buildActions(bool isFavorite) {
+ switch (type) {
+ case MobileViewCardType.recent:
+ return [
+ isFavorite
+ ? MobileViewItemBottomSheetBodyAction.removeFromFavorites
+ : MobileViewItemBottomSheetBodyAction.addToFavorites,
+ MobileViewItemBottomSheetBodyAction.divider,
+ MobileViewItemBottomSheetBodyAction.duplicate,
+ MobileViewItemBottomSheetBodyAction.divider,
+ MobileViewItemBottomSheetBodyAction.removeFromRecent,
+ ];
+ case MobileViewCardType.favorite:
+ return [
+ MobileViewItemBottomSheetBodyAction.removeFromFavorites,
+ MobileViewItemBottomSheetBodyAction.divider,
+ MobileViewItemBottomSheetBodyAction.duplicate,
+ ];
+ }
+ }
+}
+
+class _ViewCover extends StatelessWidget {
+ const _ViewCover({
+ required this.coverTypeV1,
+ this.coverTypeV2,
+ this.value,
+ });
+
+ final CoverType coverTypeV1;
+ final PageStyleCoverImageType? coverTypeV2;
+ final String? value;
+
+ @override
+ Widget build(BuildContext context) {
+ final placeholder = Container(
+ color: const Color(0xFFE1FBFF),
+ );
+ final value = this.value;
+ if (value == null) {
+ return placeholder;
+ }
+ if (coverTypeV2 != null) {
+ return _buildCoverV2(context, value, placeholder);
+ }
+ return _buildCoverV1(context, value, placeholder);
+ }
+
+ Widget _buildCoverV2(BuildContext context, String value, Widget placeholder) {
+ final type = coverTypeV2;
+ if (type == null) {
+ return placeholder;
+ }
+ if (type == PageStyleCoverImageType.customImage ||
+ type == PageStyleCoverImageType.unsplashImage) {
+ final userProfilePB = Provider.of(context);
+ return FlowyNetworkImage(
+ url: value,
+ userProfilePB: userProfilePB,
+ );
+ }
+
+ if (type == PageStyleCoverImageType.builtInImage) {
+ return Image.asset(
+ PageStyleCoverImageType.builtInImagePath(value),
+ fit: BoxFit.cover,
+ );
+ }
+
+ if (type == PageStyleCoverImageType.pureColor) {
+ final color = value.coverColor(context);
+ if (color != null) {
+ return ColoredBox(
+ color: color,
+ );
+ }
+ }
+
+ if (type == PageStyleCoverImageType.gradientColor) {
+ return Container(
+ decoration: BoxDecoration(
+ gradient: FlowyGradientColor.fromId(value).linear,
+ ),
+ );
+ }
+
+ if (type == PageStyleCoverImageType.localImage) {
+ return Image.file(
+ File(value),
+ fit: BoxFit.cover,
+ );
+ }
+
+ return placeholder;
+ }
+
+ Widget _buildCoverV1(BuildContext context, String value, Widget placeholder) {
+ switch (coverTypeV1) {
+ case CoverType.file:
+ if (isURL(value)) {
+ final userProfilePB = Provider.of(context);
+ return FlowyNetworkImage(
+ url: value,
+ userProfilePB: userProfilePB,
+ );
+ }
+ final imageFile = File(value);
+ if (!imageFile.existsSync()) {
+ return placeholder;
+ }
+ return Image.file(
+ imageFile,
+ );
+ case CoverType.asset:
+ return Image.asset(
+ value,
+ fit: BoxFit.cover,
+ );
+ case CoverType.color:
+ final color = value.tryToColor() ?? Colors.white;
+ return Container(
+ color: color,
+ );
+ case CoverType.none:
+ return placeholder;
+ }
+ }
+}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/_round_underline_tab_indicator.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/_round_underline_tab_indicator.dart
new file mode 100644
index 0000000000000..1a3eb121f3b77
--- /dev/null
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/_round_underline_tab_indicator.dart
@@ -0,0 +1,101 @@
+import 'package:flutter/material.dart';
+
+class RoundUnderlineTabIndicator extends Decoration {
+ const RoundUnderlineTabIndicator({
+ this.borderRadius,
+ this.borderSide = const BorderSide(width: 2.0, color: Colors.white),
+ this.insets = EdgeInsets.zero,
+ required this.width,
+ });
+
+ final BorderRadius? borderRadius;
+ final BorderSide borderSide;
+ final EdgeInsetsGeometry insets;
+ final double width;
+
+ @override
+ Decoration? lerpFrom(Decoration? a, double t) {
+ if (a is UnderlineTabIndicator) {
+ return UnderlineTabIndicator(
+ borderSide: BorderSide.lerp(a.borderSide, borderSide, t),
+ insets: EdgeInsetsGeometry.lerp(a.insets, insets, t)!,
+ );
+ }
+ return super.lerpFrom(a, t);
+ }
+
+ @override
+ Decoration? lerpTo(Decoration? b, double t) {
+ if (b is UnderlineTabIndicator) {
+ return UnderlineTabIndicator(
+ borderSide: BorderSide.lerp(borderSide, b.borderSide, t),
+ insets: EdgeInsetsGeometry.lerp(insets, b.insets, t)!,
+ );
+ }
+ return super.lerpTo(b, t);
+ }
+
+ @override
+ BoxPainter createBoxPainter([VoidCallback? onChanged]) {
+ return _UnderlinePainter(this, borderRadius, onChanged);
+ }
+
+ Rect _indicatorRectFor(Rect rect, TextDirection textDirection) {
+ final Rect indicator = insets.resolve(textDirection).deflateRect(rect);
+ final center = indicator.center.dx;
+ return Rect.fromLTWH(
+ center - width / 2.0,
+ indicator.bottom - borderSide.width,
+ width,
+ borderSide.width,
+ );
+ }
+
+ @override
+ Path getClipPath(Rect rect, TextDirection textDirection) {
+ if (borderRadius != null) {
+ return Path()
+ ..addRRect(
+ borderRadius!.toRRect(_indicatorRectFor(rect, textDirection)),
+ );
+ }
+ return Path()..addRect(_indicatorRectFor(rect, textDirection));
+ }
+}
+
+class _UnderlinePainter extends BoxPainter {
+ _UnderlinePainter(
+ this.decoration,
+ this.borderRadius,
+ super.onChanged,
+ );
+
+ final RoundUnderlineTabIndicator decoration;
+ final BorderRadius? borderRadius;
+
+ @override
+ void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
+ assert(configuration.size != null);
+ final Rect rect = offset & configuration.size!;
+ final TextDirection textDirection = configuration.textDirection!;
+ final Paint paint;
+ if (borderRadius != null) {
+ paint = Paint()..color = decoration.borderSide.color;
+ final Rect indicator = decoration._indicatorRectFor(rect, textDirection);
+ final RRect rrect = RRect.fromRectAndCorners(
+ indicator,
+ topLeft: borderRadius!.topLeft,
+ topRight: borderRadius!.topRight,
+ bottomRight: borderRadius!.bottomRight,
+ bottomLeft: borderRadius!.bottomLeft,
+ );
+ canvas.drawRRect(rrect, paint);
+ } else {
+ paint = decoration.borderSide.toPaint()..strokeCap = StrokeCap.round;
+ final Rect indicator = decoration
+ ._indicatorRectFor(rect, textDirection)
+ .deflate(decoration.borderSide.width / 2.0);
+ canvas.drawLine(indicator.bottomLeft, indicator.bottomRight, paint);
+ }
+ }
+}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/_tab_bar.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/_tab_bar.dart
new file mode 100644
index 0000000000000..adf13ed66782d
--- /dev/null
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/_tab_bar.dart
@@ -0,0 +1,57 @@
+import 'package:appflowy/mobile/presentation/home/tab/_round_underline_tab_indicator.dart';
+import 'package:appflowy/mobile/presentation/home/tab/space_order_bloc.dart';
+import 'package:flutter/material.dart';
+import 'package:reorderable_tabbar/reorderable_tabbar.dart';
+
+class MobileSpaceTabBar extends StatelessWidget {
+ const MobileSpaceTabBar({
+ super.key,
+ this.height = 38.0,
+ required this.tabController,
+ required this.tabs,
+ required this.onReorder,
+ });
+
+ final double height;
+ final List tabs;
+ final TabController tabController;
+ final OnReorder onReorder;
+
+ @override
+ Widget build(BuildContext context) {
+ final baseStyle = Theme.of(context).textTheme.bodyMedium;
+ final labelStyle = baseStyle?.copyWith(
+ fontWeight: FontWeight.w500,
+ fontSize: 15.0,
+ );
+ final unselectedLabelStyle = baseStyle?.copyWith(
+ fontWeight: FontWeight.w400,
+ fontSize: 15.0,
+ );
+
+ return Container(
+ height: height,
+ padding: const EdgeInsets.only(left: 8.0),
+ child: ReorderableTabBar(
+ controller: tabController,
+ tabs: tabs.map((e) => Tab(text: e.tr)).toList(),
+ indicatorSize: TabBarIndicatorSize.label,
+ indicatorColor: Theme.of(context).primaryColor,
+ isScrollable: true,
+ labelStyle: labelStyle,
+ labelColor: baseStyle?.color,
+ labelPadding: const EdgeInsets.symmetric(horizontal: 12.0),
+ unselectedLabelStyle: unselectedLabelStyle,
+ overlayColor: WidgetStateProperty.all(Colors.transparent),
+ indicator: RoundUnderlineTabIndicator(
+ width: 28.0,
+ borderSide: BorderSide(
+ color: Theme.of(context).primaryColor,
+ width: 3,
+ ),
+ ),
+ onReorder: onReorder,
+ ),
+ );
+ }
+}
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
new file mode 100644
index 0000000000000..097bd22910826
--- /dev/null
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/mobile_space_tab.dart
@@ -0,0 +1,107 @@
+import 'package:appflowy/mobile/presentation/home/favorite_folder/favorite_space.dart';
+import 'package:appflowy/mobile/presentation/home/home_space/home_space.dart';
+import 'package:appflowy/mobile/presentation/home/recent_folder/recent_space.dart';
+import 'package:appflowy/mobile/presentation/home/tab/_tab_bar.dart';
+import 'package:appflowy/mobile/presentation/home/tab/space_order_bloc.dart';
+import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:provider/provider.dart';
+
+class MobileSpaceTab extends StatefulWidget {
+ const MobileSpaceTab({super.key, required this.userProfile});
+
+ final UserProfilePB userProfile;
+
+ @override
+ State createState() => _MobileSpaceTabState();
+}
+
+class _MobileSpaceTabState extends State
+ with SingleTickerProviderStateMixin {
+ TabController? tabController;
+
+ @override
+ void dispose() {
+ tabController?.removeListener(_onTabChange);
+ tabController?.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Provider.value(
+ value: widget.userProfile,
+ child: BlocBuilder(
+ builder: (context, state) {
+ if (state.isLoading) {
+ return const SizedBox.shrink();
+ }
+
+ _initTabController(state);
+
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ MobileSpaceTabBar(
+ tabController: tabController!,
+ tabs: state.tabsOrder,
+ onReorder: (from, to) {
+ context.read().add(
+ SpaceOrderEvent.reorder(from, to),
+ );
+ },
+ ),
+ const HSpace(12.0),
+ Expanded(
+ child: TabBarView(
+ controller: tabController,
+ children: _buildTabs(state),
+ ),
+ ),
+ ],
+ );
+ },
+ ),
+ );
+ }
+
+ void _initTabController(SpaceOrderState state) {
+ if (tabController != null) {
+ return;
+ }
+ tabController = TabController(
+ length: state.tabsOrder.length,
+ vsync: this,
+ initialIndex: state.tabsOrder.indexOf(state.defaultTab),
+ );
+ tabController?.addListener(_onTabChange);
+ }
+
+ void _onTabChange() {
+ if (tabController == null) {
+ return;
+ }
+ context.read().add(
+ SpaceOrderEvent.open(
+ tabController!.index,
+ ),
+ );
+ }
+
+ List _buildTabs(SpaceOrderState state) {
+ return state.tabsOrder.map((tab) {
+ switch (tab) {
+ case MobileSpaceTabType.recent:
+ return const MobileRecentSpace();
+ case MobileSpaceTabType.spaces:
+ return MobileHomeSpace(userProfile: widget.userProfile);
+ case MobileSpaceTabType.favorites:
+ return MobileFavoriteSpace(userProfile: widget.userProfile);
+ default:
+ throw Exception('Unknown tab type: $tab');
+ }
+ }).toList();
+ }
+}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/space_order_bloc.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/space_order_bloc.dart
new file mode 100644
index 0000000000000..e3c1439dd45e6
--- /dev/null
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/space_order_bloc.dart
@@ -0,0 +1,127 @@
+import 'dart:convert';
+
+import 'package:appflowy/core/config/kv.dart';
+import 'package:appflowy/core/config/kv_keys.dart';
+import 'package:appflowy/generated/locale_keys.g.dart';
+import 'package:appflowy/startup/startup.dart';
+import 'package:bloc/bloc.dart';
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/foundation.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+part 'space_order_bloc.freezed.dart';
+
+enum MobileSpaceTabType {
+ // DO NOT CHANGE THE ORDER
+ spaces,
+ recent,
+ favorites;
+
+ String get tr {
+ switch (this) {
+ case MobileSpaceTabType.recent:
+ return LocaleKeys.sideBar_RecentSpace.tr();
+ case MobileSpaceTabType.spaces:
+ return LocaleKeys.sideBar_Spaces.tr();
+ case MobileSpaceTabType.favorites:
+ return LocaleKeys.sideBar_favoriteSpace.tr();
+ }
+ }
+}
+
+class SpaceOrderBloc extends Bloc {
+ SpaceOrderBloc() : super(const SpaceOrderState()) {
+ on(
+ (event, emit) async {
+ await event.when(
+ initial: () async {
+ final tabsOrder = await _getTabsOrder();
+ final defaultTab = await _getDefaultTab();
+ emit(
+ state.copyWith(
+ tabsOrder: tabsOrder,
+ defaultTab: defaultTab,
+ isLoading: false,
+ ),
+ );
+ },
+ open: (index) async {
+ final tab = state.tabsOrder[index];
+ await _setDefaultTab(tab);
+ },
+ reorder: (from, to) async {
+ final tabsOrder = List.of(state.tabsOrder);
+ tabsOrder.insert(to, tabsOrder.removeAt(from));
+ await _setTabsOrder(tabsOrder);
+ emit(state.copyWith(tabsOrder: tabsOrder));
+ },
+ );
+ },
+ );
+ }
+
+ final _storage = getIt();
+
+ Future _getDefaultTab() async {
+ try {
+ return await _storage.getWithFormat(
+ KVKeys.lastOpenedSpace, (value) {
+ return MobileSpaceTabType.values[int.parse(value)];
+ }) ??
+ MobileSpaceTabType.spaces;
+ } catch (e) {
+ return MobileSpaceTabType.spaces;
+ }
+ }
+
+ Future _setDefaultTab(MobileSpaceTabType tab) async {
+ await _storage.set(
+ KVKeys.lastOpenedSpace,
+ tab.index.toString(),
+ );
+ }
+
+ Future> _getTabsOrder() async {
+ try {
+ return await _storage.getWithFormat>(
+ KVKeys.spaceOrder, (value) {
+ final order = jsonDecode(value).cast();
+ if (order.isEmpty) {
+ return MobileSpaceTabType.values;
+ }
+ return order
+ .map((e) => MobileSpaceTabType.values[e])
+ .cast()
+ .toList();
+ }) ??
+ MobileSpaceTabType.values;
+ } catch (e) {
+ return MobileSpaceTabType.values;
+ }
+ }
+
+ Future _setTabsOrder(List tabsOrder) async {
+ await _storage.set(
+ KVKeys.spaceOrder,
+ jsonEncode(tabsOrder.map((e) => e.index).toList()),
+ );
+ }
+}
+
+@freezed
+class SpaceOrderEvent with _$SpaceOrderEvent {
+ const factory SpaceOrderEvent.initial() = Initial;
+ const factory SpaceOrderEvent.open(int index) = Open;
+ const factory SpaceOrderEvent.reorder(int from, int to) = Reorder;
+}
+
+@freezed
+class SpaceOrderState with _$SpaceOrderState {
+ const factory SpaceOrderState({
+ @Default(MobileSpaceTabType.spaces) MobileSpaceTabType defaultTab,
+ @Default(MobileSpaceTabType.values) List tabsOrder,
+ @Default(true) bool isLoading,
+ }) = _SpaceOrderState;
+
+ factory SpaceOrderState.initial() => const SpaceOrderState();
+}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/workspaces/workspace_menu_bottom_sheet.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/workspaces/workspace_menu_bottom_sheet.dart
index 4745b248c3b49..496c5607a583f 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/home/workspaces/workspace_menu_bottom_sheet.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/workspaces/workspace_menu_bottom_sheet.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/mobile/presentation/widgets/widgets.dart';
@@ -9,6 +7,7 @@ import 'package:appflowy/workspace/presentation/settings/widgets/members/workspa
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
// Only works on mobile.
@@ -106,6 +105,7 @@ class _WorkspaceMenuItem extends StatelessWidget {
leftIcon: WorkspaceIcon(
enableEdit: false,
iconSize: 26,
+ fontSize: 16.0,
workspace: workspace,
onSelected: (result) => context.read().add(
UserWorkspaceEvent.updateWorkspaceIcon(
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/mobile_bottom_navigation_bar.dart b/frontend/appflowy_flutter/lib/mobile/presentation/mobile_bottom_navigation_bar.dart
index 101a546294b75..3115428eb5264 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/mobile_bottom_navigation_bar.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/mobile_bottom_navigation_bar.dart
@@ -20,49 +20,40 @@ class MobileBottomNavigationBar extends StatelessWidget {
return Scaffold(
body: navigationShell,
- bottomNavigationBar: BottomNavigationBar(
- showSelectedLabels: false,
- showUnselectedLabels: false,
- enableFeedback: true,
- type: BottomNavigationBarType.fixed,
- items: [
- BottomNavigationBarItem(
- // There is no text shown on the bottom navigation bar, but Exception will be thrown if label is null here.
- label: 'home',
- icon: const FlowySvg(FlowySvgs.m_home_unselected_lg),
- activeIcon: FlowySvg(
- FlowySvgs.m_home_selected_lg,
- color: style.colorScheme.primary,
+ bottomNavigationBar: Theme(
+ data: Theme.of(context).copyWith(
+ splashColor: Colors.transparent,
+ highlightColor: Colors.transparent,
+ ),
+ child: BottomNavigationBar(
+ showSelectedLabels: false,
+ showUnselectedLabels: false,
+ enableFeedback: false,
+ type: BottomNavigationBarType.fixed,
+ elevation: 0,
+ items: [
+ const BottomNavigationBarItem(
+ label: 'home',
+ icon: FlowySvg(FlowySvgs.m_home_unselected_m),
+ activeIcon:
+ FlowySvg(FlowySvgs.m_home_selected_m, blendMode: null),
),
- ),
- const BottomNavigationBarItem(
- label: 'favorite',
- icon: FlowySvg(FlowySvgs.m_favorite_unselected_lg),
- activeIcon: FlowySvg(
- FlowySvgs.m_favorite_selected_lg,
- blendMode: null,
+ const BottomNavigationBarItem(
+ label: 'add',
+ icon: FlowySvg(FlowySvgs.m_home_add_m),
),
- ),
- // Enable this when search is ready.
- // BottomNavigationBarItem(
- // label: 'search',
- // icon: const FlowySvg(FlowySvgs.m_search_lg),
- // activeIcon: FlowySvg(
- // FlowySvgs.m_search_lg,
- // color: style.colorScheme.primary,
- // ),
- // ),
- BottomNavigationBarItem(
- label: 'notification',
- icon: const FlowySvg(FlowySvgs.m_notification_unselected_lg),
- activeIcon: FlowySvg(
- FlowySvgs.m_notification_selected_lg,
- color: style.colorScheme.primary,
+ BottomNavigationBarItem(
+ label: 'notification',
+ icon: const FlowySvg(FlowySvgs.m_home_notification_m),
+ activeIcon: FlowySvg(
+ FlowySvgs.m_home_notification_m,
+ color: style.colorScheme.primary,
+ ),
),
- ),
- ],
- currentIndex: navigationShell.currentIndex,
- onTap: (int bottomBarIndex) => _onTap(context, bottomBarIndex),
+ ],
+ currentIndex: navigationShell.currentIndex,
+ onTap: (int bottomBarIndex) => _onTap(context, bottomBarIndex),
+ ),
),
);
}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/page_item/mobile_view_item.dart b/frontend/appflowy_flutter/lib/mobile/presentation/page_item/mobile_view_item.dart
index 34fd5176133f3..7bd7a50157869 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/page_item/mobile_view_item.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/page_item/mobile_view_item.dart
@@ -1,31 +1,30 @@
-import 'package:flutter/material.dart';
-
+import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/mobile/presentation/page_item/mobile_view_item_add_button.dart';
-import 'package:appflowy/plugins/base/emoji/emoji_text.dart';
+import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
import 'package:appflowy/workspace/application/view/view_bloc.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
+import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/draggable_view_item.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
typedef ViewItemOnSelected = void Function(ViewPB);
typedef ActionPaneBuilder = ActionPane Function(BuildContext context);
-const _itemHeight = 48.0;
-
class MobileViewItem extends StatelessWidget {
const MobileViewItem({
super.key,
required this.view,
this.parentView,
- required this.categoryType,
+ required this.spaceType,
required this.level,
this.leftPadding = 10,
required this.onSelected,
@@ -39,7 +38,7 @@ class MobileViewItem extends StatelessWidget {
final ViewPB view;
final ViewPB? parentView;
- final FolderCategoryType categoryType;
+ final FolderSpaceType spaceType;
// indicate the level of the view item
// used to calculate the left padding
@@ -80,7 +79,7 @@ class MobileViewItem extends StatelessWidget {
view: state.view,
parentView: parentView,
childViews: state.view.childViews,
- categoryType: categoryType,
+ spaceType: spaceType,
level: level,
leftPadding: leftPadding,
showActions: true,
@@ -104,7 +103,7 @@ class InnerMobileViewItem extends StatelessWidget {
required this.view,
required this.parentView,
required this.childViews,
- required this.categoryType,
+ required this.spaceType,
this.isDraggable = true,
this.isExpanded = true,
required this.level,
@@ -120,7 +119,7 @@ class InnerMobileViewItem extends StatelessWidget {
final ViewPB view;
final ViewPB? parentView;
final List childViews;
- final FolderCategoryType categoryType;
+ final FolderSpaceType spaceType;
final bool isDraggable;
final bool isExpanded;
@@ -144,7 +143,7 @@ class InnerMobileViewItem extends StatelessWidget {
parentView: parentView,
level: level,
showActions: showActions,
- categoryType: categoryType,
+ spaceType: spaceType,
onSelected: onSelected,
isExpanded: isExpanded,
isDraggable: isDraggable,
@@ -159,9 +158,9 @@ class InnerMobileViewItem extends StatelessWidget {
if (childViews.isNotEmpty) {
final children = childViews.map((childView) {
return MobileViewItem(
- key: ValueKey('${categoryType.name} ${childView.id}'),
+ key: ValueKey('${spaceType.name} ${childView.id}'),
parentView: view,
- categoryType: categoryType,
+ spaceType: spaceType,
isFirstChild: childView.id == childViews.first.id,
view: childView,
level: level + 1,
@@ -178,48 +177,10 @@ class InnerMobileViewItem extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
child,
- const Divider(
- height: 1,
- ),
...children,
],
);
- } else {
- child = Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- child,
- const Divider(
- height: 1,
- ),
- Container(
- height: _itemHeight,
- alignment: Alignment.centerLeft,
- child: Padding(
- padding: EdgeInsets.only(left: (level + 2) * leftPadding),
- child: FlowyText.medium(
- LocaleKeys.noPagesInside.tr(),
- color: Colors.grey,
- ),
- ),
- ),
- const Divider(
- height: 1,
- ),
- ],
- );
}
- } else {
- child = Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- child,
- const Divider(
- height: 1,
- ),
- ],
- );
}
// wrap the child with DraggableItem if isDraggable is true
@@ -227,7 +188,6 @@ class InnerMobileViewItem extends StatelessWidget {
child = DraggableViewItem(
isFirstChild: isFirstChild,
view: view,
- // FIXME: use better color
centerHighlightColor: Colors.blue.shade200,
topHighlightColor: Colors.blue.shade200,
bottomHighlightColor: Colors.blue.shade200,
@@ -235,7 +195,7 @@ class InnerMobileViewItem extends StatelessWidget {
return MobileViewItem(
view: view,
parentView: parentView,
- categoryType: categoryType,
+ spaceType: spaceType,
level: level,
onSelected: onSelected,
isDraggable: false,
@@ -262,7 +222,7 @@ class SingleMobileInnerViewItem extends StatefulWidget {
required this.level,
required this.leftPadding,
this.isDraggable = true,
- required this.categoryType,
+ required this.spaceType,
required this.showActions,
required this.onSelected,
required this.isFeedback,
@@ -282,7 +242,7 @@ class SingleMobileInnerViewItem extends StatefulWidget {
final bool isDraggable;
final bool showActions;
final ViewItemOnSelected onSelected;
- final FolderCategoryType categoryType;
+ final FolderSpaceType spaceType;
final ActionPaneBuilder? startActionPane;
final ActionPaneBuilder? endActionPane;
@@ -297,15 +257,14 @@ class _SingleMobileInnerViewItemState extends State {
final children = [
// expand icon
_buildLeftIcon(),
- const HSpace(4),
// icon
_buildViewIcon(),
const HSpace(8),
// title
Expanded(
- child: FlowyText.medium(
+ child: FlowyText.regular(
widget.view.name,
- fontSize: 18.0,
+ fontSize: 16.0,
overflow: TextOverflow.ellipsis,
),
),
@@ -314,10 +273,11 @@ class _SingleMobileInnerViewItemState extends State {
// hover action
// ··· more action button
- // children.add(_buildViewMoreActionButton(context));
+ children.add(_buildViewMoreButton(context));
// only support add button for document layout
if (!widget.isFeedback && widget.view.layout == ViewLayoutPB.Document) {
// + button
+
children.add(_buildViewAddButton(context));
}
@@ -325,7 +285,7 @@ class _SingleMobileInnerViewItemState extends State {
borderRadius: BorderRadius.circular(4.0),
onTap: () => widget.onSelected(widget.view),
child: SizedBox(
- height: _itemHeight,
+ height: HomeSpaceViewSizes.mViewHeight,
child: Padding(
padding: EdgeInsets.only(left: widget.level * widget.leftPadding),
child: Row(
@@ -350,12 +310,12 @@ class _SingleMobileInnerViewItemState extends State {
Widget _buildViewIcon() {
final icon = widget.view.icon.value.isNotEmpty
- ? EmojiText(
- emoji: widget.view.icon.value,
- fontSize: 24.0,
+ ? FlowyText.emoji(
+ widget.view.icon.value,
+ fontSize: 20.0,
)
: SizedBox.square(
- dimension: 26.0,
+ dimension: 18.0,
child: widget.view.defaultIcon(),
);
return icon;
@@ -365,18 +325,20 @@ class _SingleMobileInnerViewItemState extends State {
// show > if the view is expandable.
// show · if the view can't contain child views.
Widget _buildLeftIcon() {
- if (isReferencedDatabaseView(widget.view, widget.parentView)) {
- return const _DotIconWidget();
+ if (context.read().state.view.childViews.isEmpty) {
+ return HSpace(widget.leftPadding);
}
return GestureDetector(
- child: AnimatedRotation(
- duration: const Duration(milliseconds: 250),
- turns: widget.isExpanded ? 0 : -0.25,
- child: const Icon(
- Icons.keyboard_arrow_down_rounded,
- size: 28,
- ),
+ behavior: HitTestBehavior.opaque,
+ child: Padding(
+ padding: const EdgeInsets.only(right: 6.0, top: 6.0, bottom: 6.0),
+ child: FlowySvg(
+ widget.isExpanded
+ ? FlowySvgs.m_expand_s
+ : FlowySvgs.m_collapse_s,
+ blendMode: null,
+ ),
),
onTap: () {
context
@@ -407,10 +369,9 @@ class _SingleMobileInnerViewItemState extends State {
ViewEvent.createView(
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
layout,
- section:
- widget.categoryType != FolderCategoryType.favorite
- ? widget.categoryType.toViewSectionPB
- : null,
+ section: widget.spaceType != FolderSpaceType.favorite
+ ? widget.spaceType.toViewSectionPB
+ : null,
),
);
},
@@ -420,23 +381,49 @@ class _SingleMobileInnerViewItemState extends State {
},
);
}
-}
-class _DotIconWidget extends StatelessWidget {
- const _DotIconWidget();
+ // + button
+ Widget _buildViewMoreButton(BuildContext context) {
+ return MobileViewMoreButton(onPressed: () => _showMoreActions(context));
+ }
- @override
- Widget build(BuildContext context) {
- return Padding(
- padding: const EdgeInsets.all(6.0),
- child: Container(
- width: 4,
- height: 4,
- decoration: BoxDecoration(
- color: Theme.of(context).iconTheme.color,
- borderRadius: BorderRadius.circular(2),
- ),
- ),
+ Future _showMoreActions(BuildContext context) async {
+ final viewBloc = context.read();
+ final favoriteBloc = context.read();
+ await showMobileBottomSheet(
+ context,
+ showHeader: true,
+ title: widget.view.name,
+ showDragHandle: true,
+ showCloseButton: true,
+ useRootNavigator: true,
+ builder: (context) {
+ return MultiBlocProvider(
+ providers: [
+ BlocProvider.value(value: viewBloc),
+ BlocProvider.value(value: favoriteBloc),
+ ],
+ child: BlocBuilder(
+ builder: (context, state) {
+ final isFavorite = state.view.isFavorite;
+ return MobileViewItemBottomSheet(
+ view: viewBloc.state.view,
+ actions: [
+ isFavorite
+ ? MobileViewItemBottomSheetBodyAction.removeFromFavorites
+ : MobileViewItemBottomSheetBodyAction.addToFavorites,
+ MobileViewItemBottomSheetBodyAction.divider,
+ MobileViewItemBottomSheetBodyAction.rename,
+ MobileViewItemBottomSheetBodyAction.divider,
+ MobileViewItemBottomSheetBodyAction.duplicate,
+ MobileViewItemBottomSheetBodyAction.divider,
+ MobileViewItemBottomSheetBodyAction.delete,
+ ],
+ );
+ },
+ ),
+ );
+ },
);
}
}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/page_item/mobile_view_item_add_button.dart b/frontend/appflowy_flutter/lib/mobile/presentation/page_item/mobile_view_item_add_button.dart
index eb2f8ea9f8876..77bb57773fc0a 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/page_item/mobile_view_item_add_button.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/page_item/mobile_view_item_add_button.dart
@@ -1,9 +1,8 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
+import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
-const _iconSize = 32.0;
-
class MobileViewAddButton extends StatelessWidget {
const MobileViewAddButton({
super.key,
@@ -15,12 +14,31 @@ class MobileViewAddButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FlowyIconButton(
- iconPadding: const EdgeInsets.all(2),
- width: _iconSize,
- height: _iconSize,
+ width: HomeSpaceViewSizes.mViewButtonDimension,
+ height: HomeSpaceViewSizes.mViewButtonDimension,
+ icon: const FlowySvg(
+ FlowySvgs.m_space_add_s,
+ ),
+ onPressed: onPressed,
+ );
+ }
+}
+
+class MobileViewMoreButton extends StatelessWidget {
+ const MobileViewMoreButton({
+ super.key,
+ required this.onPressed,
+ });
+
+ final VoidCallback onPressed;
+
+ @override
+ Widget build(BuildContext context) {
+ return FlowyIconButton(
+ width: HomeSpaceViewSizes.mViewButtonDimension,
+ height: HomeSpaceViewSizes.mViewButtonDimension,
icon: const FlowySvg(
- FlowySvgs.add_s,
- size: Size.square(_iconSize),
+ FlowySvgs.m_space_more_s,
),
onPressed: onPressed,
);
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/appearance/rtl_setting.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/appearance/rtl_setting.dart
index 39a0fdae4c055..e61281c5c4cff 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/appearance/rtl_setting.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/appearance/rtl_setting.dart
@@ -37,7 +37,6 @@ class RTLSetting extends StatelessWidget {
showHeader: true,
showDragHandle: true,
showDivider: false,
- showCloseButton: false,
title: LocaleKeys.settings_appearance_textDirection_label.tr(),
builder: (context) {
final layoutDirection =
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/appearance/text_scale_setting.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/appearance/text_scale_setting.dart
index e3526c3df03cd..3bdb836a71861 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/appearance/text_scale_setting.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/appearance/text_scale_setting.dart
@@ -45,7 +45,6 @@ class TextScaleSetting extends StatelessWidget {
showHeader: true,
showDragHandle: true,
showDivider: false,
- showCloseButton: false,
title: LocaleKeys.settings_appearance_fontScaleFactor.tr(),
builder: (context) {
return FontSizeStepper(
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/appearance/theme_setting.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/appearance/theme_setting.dart
index 1291804af6e7b..8893eab1059b5 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/appearance/theme_setting.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/appearance/theme_setting.dart
@@ -38,7 +38,6 @@ class ThemeSetting extends StatelessWidget {
showHeader: true,
showDragHandle: true,
showDivider: false,
- showCloseButton: false,
title: LocaleKeys.settings_appearance_themeMode_label.tr(),
builder: (context) {
final themeMode =
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/font/font_picker_screen.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/font/font_picker_screen.dart
index 64dd62729c7f8..390f0824de590 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/font/font_picker_screen.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/font/font_picker_screen.dart
@@ -29,9 +29,7 @@ class FontPickerScreen extends StatelessWidget {
}
class LanguagePickerPage extends StatefulWidget {
- const LanguagePickerPage({
- super.key,
- });
+ const LanguagePickerPage({super.key});
@override
State createState() => _LanguagePickerPageState();
@@ -43,7 +41,6 @@ class _LanguagePickerPageState extends State {
@override
void initState() {
super.initState();
-
availableFonts = _availableFonts;
}
@@ -90,7 +87,6 @@ class _FontSelectorState extends State {
@override
void initState() {
super.initState();
-
availableFonts = _availableFonts;
}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart b/frontend/appflowy_flutter/lib/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart
index 17b61849da07e..5be3d3e78c610 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart
@@ -1,7 +1,6 @@
-import 'package:flutter/material.dart';
-
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flutter/material.dart';
class MobileQuickActionButton extends StatelessWidget {
const MobileQuickActionButton({
@@ -29,23 +28,23 @@ class MobileQuickActionButton extends StatelessWidget {
onTap: enable ? onTap : null,
borderRadius: BorderRadius.circular(12),
overlayColor:
- enable ? null : const MaterialStatePropertyAll(Colors.transparent),
+ enable ? null : const WidgetStatePropertyAll(Colors.transparent),
splashColor: Colors.transparent,
child: Container(
- height: 44,
+ height: 52,
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
children: [
FlowySvg(
icon,
- size: const Size.square(20),
+ size: const Size.square(18),
color: enable ? iconColor : Theme.of(context).disabledColor,
),
const HSpace(12),
Expanded(
- child: FlowyText(
+ child: FlowyText.regular(
text,
- fontSize: 15,
+ fontSize: 16,
color: enable ? textColor : Theme.of(context).disabledColor,
),
),
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 5a481eaa6867f..321632a36a490 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
@@ -1,6 +1,8 @@
import 'package:appflowy/generated/locale_keys.g.dart';
+import 'package:appflowy/startup/tasks/app_widget.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
enum ConfirmDialogActionAlignment {
@@ -85,3 +87,46 @@ Future showFlowyMobileConfirmDialog(
},
);
}
+
+Future showFlowyCupertinoConfirmDialog({
+ BuildContext? context,
+ required String title,
+ required Widget leftButton,
+ required Widget rightButton,
+ void Function(BuildContext context)? onLeftButtonPressed,
+ void Function(BuildContext context)? onRightButtonPressed,
+}) {
+ return showDialog(
+ context: context ?? AppGlobals.context,
+ builder: (context) => CupertinoAlertDialog(
+ title: FlowyText.medium(
+ title,
+ fontSize: 18,
+ maxLines: 10,
+ lineHeight: 1.3,
+ ),
+ actions: [
+ CupertinoDialogAction(
+ onPressed: () {
+ if (onLeftButtonPressed != null) {
+ onLeftButtonPressed(context);
+ } else {
+ Navigator.of(context).pop();
+ }
+ },
+ child: leftButton,
+ ),
+ CupertinoDialogAction(
+ onPressed: () {
+ if (onRightButtonPressed != null) {
+ onRightButtonPressed(context);
+ } else {
+ Navigator.of(context).pop();
+ }
+ },
+ child: rightButton,
+ ),
+ ],
+ ),
+ );
+}
diff --git a/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_picker.dart b/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_picker.dart
index 1297bccc379fc..d7a7b9ceb22a9 100644
--- a/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_picker.dart
+++ b/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_picker.dart
@@ -1,12 +1,10 @@
-import 'dart:io';
-
import 'package:appflowy/plugins/base/emoji/emoji_picker_header.dart';
import 'package:appflowy/plugins/base/emoji/emoji_search_bar.dart';
import 'package:appflowy/plugins/base/emoji/emoji_skin_tone.dart';
+import 'package:flowy_infra/size.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_emoji_mart/flutter_emoji_mart.dart';
-import 'package:google_fonts/google_fonts.dart';
// use a global value to store the selected emoji to prevent reloading every time.
EmojiData? kCachedEmojiData;
@@ -27,7 +25,6 @@ class FlowyEmojiPicker extends StatefulWidget {
class _FlowyEmojiPickerState extends State {
EmojiData? emojiData;
- List? fallbackFontFamily;
@override
void initState() {
@@ -46,13 +43,6 @@ class _FlowyEmojiPickerState extends State {
},
);
}
-
- if (Platform.isAndroid || Platform.isLinux) {
- final notoColorEmoji = GoogleFonts.notoColorEmoji().fontFamily;
- if (notoColorEmoji != null) {
- fallbackFontFamily = [notoColorEmoji];
- }
- }
}
@override
@@ -82,14 +72,18 @@ class _FlowyEmojiPickerState extends State {
);
},
itemBuilder: (context, emojiId, emoji, callback) {
- return FlowyIconButton(
- iconPadding: const EdgeInsets.all(2.0),
- icon: FlowyText(
- emoji,
- fontSize: 28.0,
- fallbackFontFamily: fallbackFontFamily,
+ return SizedBox(
+ width: 36,
+ height: 36,
+ child: FlowyButton(
+ margin: EdgeInsets.zero,
+ radius: Corners.s8Border,
+ text: FlowyText.emoji(
+ emoji,
+ fontSize: 24.0,
+ ),
+ onTap: () => callback(emojiId, emoji),
),
- onPressed: () => callback(emojiId, emoji),
);
},
searchBarBuilder: (context, keyword, skinTone) {
diff --git a/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_picker_header.dart b/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_picker_header.dart
index 9619f00d30dc6..9f05c80f09ada 100644
--- a/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_picker_header.dart
+++ b/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_picker_header.dart
@@ -16,9 +16,14 @@ class FlowyEmojiHeader extends StatelessWidget {
if (PlatformExtension.isDesktopOrWeb) {
return Container(
height: 22,
- padding: const EdgeInsets.symmetric(horizontal: 8.0),
color: Theme.of(context).cardColor,
- child: FlowyText.regular(category.id),
+ child: Padding(
+ padding: const EdgeInsets.only(bottom: 4.0),
+ child: FlowyText.regular(
+ category.id,
+ color: Theme.of(context).hintColor,
+ ),
+ ),
);
} else {
return Column(
diff --git a/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_search_bar.dart b/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_search_bar.dart
index 1b01e6aee8d05..c6cec89ecc8bc 100644
--- a/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_search_bar.dart
+++ b/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_search_bar.dart
@@ -42,7 +42,7 @@ class _FlowyEmojiSearchBarState extends State {
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(
- vertical: 8.0,
+ vertical: 12.0,
horizontal: PlatformExtension.isDesktopOrWeb ? 0.0 : 8.0,
),
child: Row(
@@ -52,16 +52,15 @@ class _FlowyEmojiSearchBarState extends State {
onKeywordChanged: widget.onKeywordChanged,
),
),
- const HSpace(6.0),
+ const HSpace(8.0),
_RandomEmojiButton(
emojiData: widget.emojiData,
onRandomEmojiSelected: widget.onRandomEmojiSelected,
),
- const HSpace(6.0),
+ const HSpace(8.0),
FlowyEmojiSkinToneSelector(
onEmojiSkinToneChanged: widget.onSkinToneChanged,
),
- const HSpace(6.0),
],
),
);
@@ -79,20 +78,30 @@ class _RandomEmojiButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return FlowyTooltip(
- message: LocaleKeys.emoji_random.tr(),
- child: FlowyButton(
- useIntrinsicWidth: true,
- text: const Icon(
- Icons.shuffle_rounded,
+ return Container(
+ width: 36,
+ height: 36,
+ decoration: ShapeDecoration(
+ shape: RoundedRectangleBorder(
+ side: const BorderSide(color: Color(0x1E171717)),
+ borderRadius: BorderRadius.circular(8),
+ ),
+ ),
+ child: FlowyTooltip(
+ message: LocaleKeys.emoji_random.tr(),
+ child: FlowyButton(
+ useIntrinsicWidth: true,
+ text: const FlowySvg(
+ FlowySvgs.icon_shuffle_s,
+ ),
+ onTap: () {
+ final random = emojiData.random;
+ onRandomEmojiSelected(
+ random.$1,
+ random.$2,
+ );
+ },
),
- onTap: () {
- final random = emojiData.random;
- onRandomEmojiSelected(
- random.$1,
- random.$2,
- );
- },
),
);
}
@@ -123,32 +132,35 @@ class _SearchTextFieldState extends State<_SearchTextField> {
@override
Widget build(BuildContext context) {
- return ConstrainedBox(
- constraints: const BoxConstraints(
- maxHeight: 32.0,
- ),
+ return SizedBox(
+ height: 36.0,
child: FlowyTextField(
focusNode: focusNode,
- hintText: LocaleKeys.emoji_search.tr(),
+ hintText: LocaleKeys.search_label.tr(),
+ hintStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(
+ fontSize: 14.0,
+ fontWeight: FontWeight.w400,
+ color: Theme.of(context).hintColor,
+ ),
controller: controller,
onChanged: widget.onKeywordChanged,
prefixIcon: const Padding(
padding: EdgeInsets.only(
- left: 8.0,
- right: 4.0,
+ left: 14.0,
+ right: 8.0,
),
child: FlowySvg(
FlowySvgs.search_s,
),
),
prefixIconConstraints: const BoxConstraints(
- maxHeight: 18.0,
+ maxHeight: 20.0,
),
suffixIcon: Padding(
padding: const EdgeInsets.all(4.0),
child: FlowyButton(
text: const FlowySvg(
- FlowySvgs.close_lg,
+ FlowySvgs.m_app_bar_close_s,
),
margin: EdgeInsets.zero,
useIntrinsicWidth: true,
diff --git a/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_skin_tone.dart b/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_skin_tone.dart
index e8da112660b47..3add90773de81 100644
--- a/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_skin_tone.dart
+++ b/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_skin_tone.dart
@@ -57,7 +57,7 @@ class _FlowyEmojiSkinToneSelectorState
child: FlowyTooltip(
message: LocaleKeys.emoji_selectSkinTone.tr(),
child: _buildIconButton(
- lastSelectedEmojiSkinTone?.icon ?? '✋',
+ lastSelectedEmojiSkinTone?.icon ?? '👋',
() => controller.show(),
),
),
@@ -65,19 +65,22 @@ class _FlowyEmojiSkinToneSelectorState
}
Widget _buildIconButton(String icon, VoidCallback onPressed) {
- return FlowyIconButton(
- key: emojiSkinToneKey(icon),
- icon: Padding(
- // add a left padding to align the emoji center
- padding: const EdgeInsets.only(
- left: 3.0,
- ),
- child: FlowyText(
+ return Container(
+ width: 36,
+ height: 36,
+ decoration: BoxDecoration(
+ border: Border.all(color: const Color(0x1E171717)),
+ borderRadius: BorderRadius.circular(8),
+ ),
+ child: FlowyButton(
+ key: emojiSkinToneKey(icon),
+ margin: EdgeInsets.zero,
+ text: FlowyText.emoji(
icon,
- fontSize: 22.0,
+ fontSize: 24.0,
),
+ onTap: onPressed,
),
- onPressed: onPressed,
);
}
}
@@ -86,17 +89,17 @@ extension EmojiSkinToneIcon on EmojiSkinTone {
String get icon {
switch (this) {
case EmojiSkinTone.none:
- return '✋';
+ return '👋';
case EmojiSkinTone.light:
- return '✋🏻';
+ return '👋🏻';
case EmojiSkinTone.mediumLight:
- return '✋🏼';
+ return '👋🏼';
case EmojiSkinTone.medium:
- return '✋🏽';
+ return '👋🏽';
case EmojiSkinTone.mediumDark:
- return '✋🏾';
+ return '👋🏾';
case EmojiSkinTone.dark:
- return '✋🏿';
+ return '👋🏿';
}
}
}
diff --git a/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_text.dart b/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_text.dart
index e9fed800d48df..5481c7676ad55 100644
--- a/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_text.dart
+++ b/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_text.dart
@@ -29,6 +29,7 @@ class EmojiText extends StatelessWidget {
emoji,
fontSize: fontSize,
textAlign: textAlign,
+ strutStyle: const StrutStyle(forceStrutHeight: true),
fallbackFontFamily: _cachedFallbackFontFamily,
lineHeight: lineHeight,
);
diff --git a/frontend/appflowy_flutter/lib/plugins/base/icon/icon_picker.dart b/frontend/appflowy_flutter/lib/plugins/base/icon/icon_picker.dart
index a77b4b2f2719b..08e63251e0257 100644
--- a/frontend/appflowy_flutter/lib/plugins/base/icon/icon_picker.dart
+++ b/frontend/appflowy_flutter/lib/plugins/base/icon/icon_picker.dart
@@ -1,13 +1,10 @@
-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/base/emoji/emoji_picker.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/icon.pbenum.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:flowy_infra_ui/style_widget/hover.dart';
+import 'package:flutter/material.dart';
extension ToProto on FlowyIconType {
ViewIconTypePB toProto() {
@@ -54,57 +51,28 @@ class FlowyIconPicker extends StatelessWidget {
@override
Widget build(BuildContext context) {
- // ONLY supports emoji picker for now
- return DefaultTabController(
- length: 1,
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
+ const VSpace(8.0),
Row(
children: [
- _buildTabs(context),
+ FlowyText(LocaleKeys.newSettings_workplace_chooseAnIcon.tr()),
const Spacer(),
_RemoveIconButton(
onTap: () => onSelected(EmojiPickerResult.none()),
),
],
),
- const Divider(height: 2),
+ const VSpace(12.0),
+ const Divider(height: 0.5),
Expanded(
- child: TabBarView(
- children: [
- FlowyEmojiPicker(
- emojiPerLine: _getEmojiPerLine(context),
- onEmojiSelected: (_, emoji) =>
- onSelected(EmojiPickerResult.emoji(emoji)),
- ),
- ],
- ),
- ),
- ],
- ),
- );
- }
-
- Widget _buildTabs(BuildContext context) {
- return Align(
- alignment: Alignment.centerLeft,
- child: TabBar(
- indicatorSize: TabBarIndicatorSize.label,
- isScrollable: true,
- overlayColor: MaterialStatePropertyAll(
- Theme.of(context).colorScheme.secondary,
- ),
- padding: EdgeInsets.zero,
- tabs: [
- FlowyHover(
- style: const HoverStyle(borderRadius: BorderRadius.zero),
- child: Padding(
- padding: const EdgeInsets.symmetric(
- horizontal: 12.0,
- vertical: 8.0,
- ),
- child: FlowyText(LocaleKeys.emoji_emojiTab.tr()),
+ child: FlowyEmojiPicker(
+ emojiPerLine: _getEmojiPerLine(context),
+ onEmojiSelected: (_, emoji) =>
+ onSelected(EmojiPickerResult.emoji(emoji)),
),
),
],
@@ -117,7 +85,7 @@ class FlowyIconPicker extends StatelessWidget {
return 9;
}
final width = MediaQuery.of(context).size.width;
- return width ~/ 46.0; // the size of the emoji
+ return width ~/ 40.0; // the size of the emoji
}
}
@@ -129,14 +97,14 @@ class _RemoveIconButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SizedBox(
- height: 28,
+ height: 24,
child: FlowyButton(
onTap: onTap,
useIntrinsicWidth: true,
- text: FlowyText.small(
- LocaleKeys.document_plugins_cover_removeIcon.tr(),
+ text: FlowyText.regular(
+ LocaleKeys.button_remove.tr(),
+ color: Theme.of(context).hintColor,
),
- leftIcon: const FlowySvg(FlowySvgs.delete_s),
),
);
}
diff --git a/frontend/appflowy_flutter/lib/plugins/database/board/presentation/board_page.dart b/frontend/appflowy_flutter/lib/plugins/database/board/presentation/board_page.dart
index 64415ff85bd25..a1f3a78f75e85 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/board/presentation/board_page.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/board/presentation/board_page.dart
@@ -20,6 +20,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_board/appflowy_board.dart';
import 'package:appflowy_editor/appflowy_editor.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:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/widget/error_page.dart';
@@ -671,7 +672,7 @@ class _BoardCardState extends State<_BoardCard> {
? const Color(0x0F1F2329)
: const Color(0x0FEFF4FB),
foregroundColorOnHover:
- Theme.of(context).colorScheme.onBackground,
+ AFThemeExtension.of(context).onBackground,
),
),
onStartEditing: () =>
diff --git a/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/calendar_day.dart b/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/calendar_day.dart
index 5d1a29141e4ea..6dfa3313f56b9 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/calendar_day.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/calendar_day.dart
@@ -238,7 +238,7 @@ class NewEventButton extends StatelessWidget {
child: FlowyIconButton(
onPressed: onCreate,
icon: const FlowySvg(FlowySvgs.add_s),
- fillColor: Theme.of(context).colorScheme.background,
+ fillColor: Theme.of(context).colorScheme.surface,
hoverColor: AFThemeExtension.of(context).lightGreyHover,
width: 22,
tooltipText: LocaleKeys.calendar_newEventButtonTooltip.tr(),
@@ -289,8 +289,8 @@ class _DayBadge extends StatelessWidget {
@override
Widget build(BuildContext context) {
- Color dayTextColor = Theme.of(context).colorScheme.onBackground;
- Color monthTextColor = Theme.of(context).colorScheme.onBackground;
+ Color dayTextColor = AFThemeExtension.of(context).onBackground;
+ Color monthTextColor = AFThemeExtension.of(context).onBackground;
final String monthString =
DateFormat("MMM ", context.locale.toLanguageTag()).format(date);
final String dayString = date.day.toString();
diff --git a/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/calendar_event_card.dart b/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/calendar_event_card.dart
index 6baf2ecd2f595..e105914908aef 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/calendar_event_card.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/calendar_event_card.dart
@@ -8,6 +8,7 @@ import 'package:appflowy/workspace/application/view/view_bloc.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_popover/appflowy_popover.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/style_widget/hover.dart';
import 'package:flutter/material.dart';
@@ -15,7 +16,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import '../application/calendar_bloc.dart';
-
import 'calendar_event_editor.dart';
class EventCard extends StatefulWidget {
@@ -102,7 +102,7 @@ class _EventCardState extends State {
hoverColor: Theme.of(context).brightness == Brightness.light
? const Color(0x0F1F2329)
: const Color(0x0FEFF4FB),
- foregroundColorOnHover: Theme.of(context).colorScheme.onBackground,
+ foregroundColorOnHover: AFThemeExtension.of(context).onBackground,
),
),
onStartEditing: () {},
diff --git a/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/calendar_page.dart b/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/calendar_page.dart
index c38c7647baf41..ff695eea0e811 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/calendar_page.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/calendar_page.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/mobile/presentation/bottom_sheet/bottom_sheet.dart';
@@ -21,12 +19,12 @@ 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/flowy_tooltip.dart';
+import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import '../../application/row/row_controller.dart';
import '../../widgets/row/row_detail.dart';
-
import 'calendar_day.dart';
import 'layout/sizes.dart';
import 'toolbar/calendar_setting_bar.dart';
diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/mobile_fab.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/mobile_fab.dart
index 29af8e4355477..7c18bb927f8ad 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/mobile_fab.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/mobile_fab.dart
@@ -43,7 +43,7 @@ Widget getGridFabs(BuildContext context) {
.read()
.add(const GridEvent.createRow(openRowDetail: true));
},
- overlayColor: const MaterialStatePropertyAll(Color(0xFF009FD1)),
+ overlayColor: const WidgetStatePropertyAll(Color(0xFF009FD1)),
boxShadow: const BoxShadow(
offset: Offset(0, 8),
color: Color(0x6612BFEF),
@@ -75,7 +75,7 @@ class MobileGridFab extends StatelessWidget {
final VoidCallback onTap;
final FlowySvgData icon;
final Size iconSize;
- final MaterialStateProperty? overlayColor;
+ final WidgetStateProperty? overlayColor;
@override
Widget build(BuildContext context) {
diff --git a/frontend/appflowy_flutter/lib/plugins/database/tab_bar/mobile/mobile_tab_bar_header.dart b/frontend/appflowy_flutter/lib/plugins/database/tab_bar/mobile/mobile_tab_bar_header.dart
index 55394ec33c887..ddddd90fb4d38 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/tab_bar/mobile/mobile_tab_bar_header.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/tab_bar/mobile/mobile_tab_bar_header.dart
@@ -77,22 +77,22 @@ class _DatabaseViewSelectorButton extends StatelessWidget {
return TextButton(
style: ButtonStyle(
- padding: const MaterialStatePropertyAll(
+ padding: const WidgetStatePropertyAll(
EdgeInsets.fromLTRB(12, 8, 8, 8),
),
- maximumSize: const MaterialStatePropertyAll(Size(200, 48)),
- minimumSize: const MaterialStatePropertyAll(Size(48, 0)),
- shape: const MaterialStatePropertyAll(
+ maximumSize: const WidgetStatePropertyAll(Size(200, 48)),
+ minimumSize: const WidgetStatePropertyAll(Size(48, 0)),
+ shape: const WidgetStatePropertyAll(
RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
),
- backgroundColor: MaterialStatePropertyAll(
+ backgroundColor: WidgetStatePropertyAll(
Theme.of(context).brightness == Brightness.light
? const Color(0x0F212729)
: const Color(0x0FFFFFFF),
),
- overlayColor: MaterialStatePropertyAll(
+ overlayColor: WidgetStatePropertyAll(
Theme.of(context).colorScheme.secondary,
),
),
@@ -119,7 +119,6 @@ class _DatabaseViewSelectorButton extends StatelessWidget {
showTransitionMobileBottomSheet(
context,
showDivider: false,
- initialStop: 1.0,
builder: (_) {
return MultiBlocProvider(
providers: [
diff --git a/frontend/appflowy_flutter/lib/plugins/database/tab_bar/tab_bar_view.dart b/frontend/appflowy_flutter/lib/plugins/database/tab_bar/tab_bar_view.dart
index d8aa978e12518..787e08760b0c6 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/tab_bar/tab_bar_view.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/tab_bar/tab_bar_view.dart
@@ -236,7 +236,8 @@ class DatabasePluginWidgetBuilder extends PluginWidgetBuilder {
final String? initialRowId;
@override
- Widget get leftBarItem => ViewTitleBar(view: notifier.view);
+ Widget get leftBarItem =>
+ ViewTitleBar(key: ValueKey(notifier.view.id), view: notifier.view);
@override
Widget tabBarItem(String pluginId) => ViewTabBarItem(view: notifier.view);
@@ -278,7 +279,7 @@ class DatabasePluginWidgetBuilder extends PluginWidgetBuilder {
]
: [],
DatabaseShareButton(key: ValueKey(view.id), view: view),
- const HSpace(4),
+ const HSpace(10),
ViewFavoriteButton(view: view),
const HSpace(4),
MoreViewActions(view: view, isDocument: false),
diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_relation_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_relation_cell.dart
index 0e411440efc92..5c31d41b30eee 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_relation_cell.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_relation_cell.dart
@@ -42,7 +42,6 @@ class MobileGridRelationCellSkin extends IEditableRelationCellSkin {
onTap: () {
showMobileBottomSheet(
context,
- padding: EdgeInsets.zero,
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
builder: (context) {
return const FlowyText("Coming soon");
diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_url_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_url_cell.dart
index 4dffae30221c5..cddb8219431a4 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_url_cell.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_url_cell.dart
@@ -2,6 +2,7 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_she
import 'package:appflowy/plugins/database/application/cell/bloc/url_cell_bloc.dart';
import 'package:appflowy/plugins/database/widgets/row/accessory/cell_accessory.dart';
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
+import 'package:flowy_infra/theme_extension.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -51,7 +52,7 @@ class MobileGridURLCellSkin extends IEditableURLCellSkin {
showMobileBottomSheet(
context,
showDragHandle: true,
- backgroundColor: Theme.of(context).colorScheme.background,
+ backgroundColor: AFThemeExtension.of(context).background,
builder: (context) => BlocProvider.value(
value: bloc,
child: MobileURLEditor(
diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_checkbox_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_checkbox_cell.dart
index 23b5c31c755c2..2e9e4b1a24987 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_checkbox_cell.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_checkbox_cell.dart
@@ -1,6 +1,7 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
-import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
import 'package:appflowy/plugins/database/application/cell/bloc/checkbox_cell_bloc.dart';
+import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
+import 'package:flowy_infra/theme_extension.dart';
import 'package:flutter/material.dart';
import '../editable_cell_skeleton/checkbox.dart';
@@ -31,7 +32,7 @@ class MobileRowDetailCheckboxCellSkin extends IEditableCheckboxCellSkin {
alignment: AlignmentDirectional.centerStart,
child: FlowySvg(
state.isSelected ? FlowySvgs.check_filled_s : FlowySvgs.uncheck_s,
- color: Theme.of(context).colorScheme.onBackground,
+ color: AFThemeExtension.of(context).onBackground,
blendMode: BlendMode.dst,
size: const Size.square(24),
),
diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_checklist_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_checklist_cell.dart
index dd981795d2a54..67f9f1c53f9a5 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_checklist_cell.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_checklist_cell.dart
@@ -1,11 +1,12 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart';
-import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
import 'package:appflowy/plugins/database/application/cell/bloc/checklist_cell_bloc.dart';
import 'package:appflowy/plugins/database/widgets/cell_editor/checklist_progress_bar.dart';
import 'package:appflowy/plugins/database/widgets/cell_editor/mobile_checklist_cell_editor.dart';
+import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -25,7 +26,7 @@ class MobileRowDetailChecklistCellSkin extends IEditableChecklistCellSkin {
borderRadius: const BorderRadius.all(Radius.circular(14)),
onTap: () => showMobileBottomSheet(
context,
- backgroundColor: Theme.of(context).colorScheme.background,
+ backgroundColor: AFThemeExtension.of(context).background,
builder: (context) {
return BlocProvider.value(
value: bloc,
diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_relation_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_relation_cell.dart
index eebb3e1c75f43..cdbcef64c74b6 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_relation_cell.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_relation_cell.dart
@@ -19,7 +19,6 @@ class MobileRowDetailRelationCellSkin extends IEditableRelationCellSkin {
borderRadius: const BorderRadius.all(Radius.circular(14)),
onTap: () => showMobileBottomSheet(
context,
- padding: EdgeInsets.zero,
builder: (context) {
return const FlowyText("Coming soon");
},
diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_url_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_url_cell.dart
index f97eabe8306bd..f87b22549272c 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_url_cell.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_url_cell.dart
@@ -1,12 +1,12 @@
-import 'package:flutter/material.dart';
-
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart';
import 'package:appflowy/plugins/database/application/cell/bloc/url_cell_bloc.dart';
import 'package:appflowy/plugins/database/widgets/row/accessory/cell_accessory.dart';
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:flowy_infra/theme_extension.dart';
import '../editable_cell_skeleton/url.dart';
@@ -28,7 +28,7 @@ class MobileRowDetailURLCellSkin extends IEditableURLCellSkin {
onTap: () => showMobileBottomSheet(
context,
showDragHandle: true,
- backgroundColor: Theme.of(context).colorScheme.background,
+ backgroundColor: AFThemeExtension.of(context).background,
builder: (_) {
return BlocProvider.value(
value: bloc,
diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/checklist_cell_editor.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/checklist_cell_editor.dart
index 3ab883329a20c..18c9428eded92 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/checklist_cell_editor.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/checklist_cell_editor.dart
@@ -423,7 +423,7 @@ class _DeleteTaskButton extends StatefulWidget {
}
class _DeleteTaskButtonState extends State<_DeleteTaskButton> {
- final _materialStatesController = MaterialStatesController();
+ final _materialStatesController = WidgetStatesController();
@override
void dispose() {
@@ -438,16 +438,16 @@ class _DeleteTaskButtonState extends State<_DeleteTaskButton> {
onHover: (_) => setState(() {}),
onFocusChange: (_) => setState(() {}),
style: ButtonStyle(
- fixedSize: const MaterialStatePropertyAll(Size.square(32)),
- minimumSize: const MaterialStatePropertyAll(Size.square(32)),
- maximumSize: const MaterialStatePropertyAll(Size.square(32)),
- overlayColor: MaterialStateProperty.resolveWith((state) {
- if (state.contains(MaterialState.focused)) {
+ fixedSize: const WidgetStatePropertyAll(Size.square(32)),
+ minimumSize: const WidgetStatePropertyAll(Size.square(32)),
+ maximumSize: const WidgetStatePropertyAll(Size.square(32)),
+ overlayColor: WidgetStateProperty.resolveWith((state) {
+ if (state.contains(WidgetState.focused)) {
return AFThemeExtension.of(context).greyHover;
}
return Colors.transparent;
}),
- shape: const MaterialStatePropertyAll(
+ shape: const WidgetStatePropertyAll(
RoundedRectangleBorder(borderRadius: Corners.s6Border),
),
),
@@ -455,8 +455,8 @@ class _DeleteTaskButtonState extends State<_DeleteTaskButton> {
child: FlowySvg(
FlowySvgs.delete_s,
color: _materialStatesController.value
- .contains(MaterialState.hovered) ||
- _materialStatesController.value.contains(MaterialState.focused)
+ .contains(WidgetState.hovered) ||
+ _materialStatesController.value.contains(WidgetState.focused)
? Theme.of(context).colorScheme.error
: null,
),
diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/relation_cell_editor.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/relation_cell_editor.dart
index 70383361d7ed1..63ea44008efe0 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/relation_cell_editor.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/relation_cell_editor.dart
@@ -422,7 +422,7 @@ class _UnselectRowButton extends StatefulWidget {
}
class _UnselectRowButtonState extends State<_UnselectRowButton> {
- final _materialStatesController = MaterialStatesController();
+ final _materialStatesController = WidgetStatesController();
@override
void dispose() {
@@ -437,26 +437,25 @@ class _UnselectRowButtonState extends State<_UnselectRowButton> {
onHover: (_) => setState(() {}),
onFocusChange: (_) => setState(() {}),
style: ButtonStyle(
- fixedSize: const MaterialStatePropertyAll(Size.square(32)),
- minimumSize: const MaterialStatePropertyAll(Size.square(32)),
- maximumSize: const MaterialStatePropertyAll(Size.square(32)),
- overlayColor: MaterialStateProperty.resolveWith((state) {
- if (state.contains(MaterialState.focused)) {
+ fixedSize: const WidgetStatePropertyAll(Size.square(32)),
+ minimumSize: const WidgetStatePropertyAll(Size.square(32)),
+ maximumSize: const WidgetStatePropertyAll(Size.square(32)),
+ overlayColor: WidgetStateProperty.resolveWith((state) {
+ if (state.contains(WidgetState.focused)) {
return AFThemeExtension.of(context).greyHover;
}
return Colors.transparent;
}),
- shape: const MaterialStatePropertyAll(
+ shape: const WidgetStatePropertyAll(
RoundedRectangleBorder(borderRadius: Corners.s6Border),
),
),
statesController: _materialStatesController,
child: Container(
- color: _materialStatesController.value
- .contains(MaterialState.hovered) ||
- _materialStatesController.value.contains(MaterialState.focused)
+ color: _materialStatesController.value.contains(WidgetState.hovered) ||
+ _materialStatesController.value.contains(WidgetState.focused)
? Theme.of(context).colorScheme.primary
- : Theme.of(context).colorScheme.onBackground,
+ : AFThemeExtension.of(context).onBackground,
width: 12,
height: 1,
),
diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/select_option_cell_editor.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/select_option_cell_editor.dart
index 3094c97887bde..d3a41c6c3fc02 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/select_option_cell_editor.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/select_option_cell_editor.dart
@@ -380,7 +380,7 @@ class _SelectOptionCellState extends State<_SelectOptionCell> {
icon: FlowySvg(
FlowySvgs.three_dots_s,
size: const Size.square(16),
- color: Theme.of(context).colorScheme.onBackground,
+ color: AFThemeExtension.of(context).onBackground,
),
),
],
@@ -462,7 +462,7 @@ class SelectOptionTagCell extends StatelessWidget {
child: FlowySvg(
FlowySvgs.drag_element_s,
size: const Size.square(14),
- color: Theme.of(context).colorScheme.onBackground,
+ color: AFThemeExtension.of(context).onBackground,
),
),
),
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 969c4e6e0c054..8762918141b67 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
@@ -314,17 +314,17 @@ class ToggleHiddenFieldsVisibilityButton extends StatelessWidget {
constraints: const BoxConstraints(minWidth: double.infinity),
child: TextButton.icon(
style: Theme.of(context).textButtonTheme.style?.copyWith(
- shape: MaterialStateProperty.all(
+ shape: WidgetStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0),
),
),
- overlayColor: MaterialStateProperty.all(
+ overlayColor: WidgetStateProperty.all(
Theme.of(context).hoverColor,
),
alignment: AlignmentDirectional.centerStart,
splashFactory: NoSplash.splashFactory,
- padding: const MaterialStatePropertyAll(
+ padding: const WidgetStatePropertyAll(
EdgeInsets.symmetric(vertical: 14, horizontal: 6),
),
),
diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/mobile_database_controls.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/mobile_database_controls.dart
index d5f3ce293a1b1..f3a548932dff6 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/mobile_database_controls.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/mobile_database_controls.dart
@@ -9,6 +9,7 @@ import 'package:appflowy/plugins/database/grid/application/sort/sort_editor_bloc
import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart';
import 'package:appflowy/workspace/application/view/view_bloc.dart';
import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -128,7 +129,6 @@ void _showDatabaseFieldListFromToolbar(
showHeader: true,
showBackButton: true,
title: LocaleKeys.grid_settings_properties.tr(),
- showDivider: true,
builder: (_) {
return BlocProvider.value(
value: context.read(),
@@ -150,7 +150,7 @@ void _showEditSortPanelFromToolbar(
showDragHandle: true,
showDivider: false,
useSafeArea: false,
- backgroundColor: Theme.of(context).colorScheme.background,
+ backgroundColor: AFThemeExtension.of(context).background,
builder: (_) {
return BlocProvider.value(
value: context.read(),
diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/share_button.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/share_button.dart
index d6c84e39fe481..7ede902085e73 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/widgets/share_button.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/share_button.dart
@@ -1,5 +1,3 @@
-import 'package:flutter/material.dart';
-
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database/application/share_bloc.dart';
import 'package:appflowy/startup/startup.dart';
@@ -13,6 +11,7 @@ import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/file_picker/file_picker_service.dart';
import 'package:flowy_infra_ui/widget/rounded_button.dart';
+import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class DatabaseShareButton extends StatelessWidget {
@@ -39,11 +38,7 @@ class DatabaseShareButton extends StatelessWidget {
);
},
child: BlocBuilder(
- builder: (context, state) => ConstrainedBox(
- constraints: const BoxConstraints.expand(
- height: 30,
- width: 100,
- ),
+ builder: (context, state) => IntrinsicWidth(
child: DatabaseShareActionList(view: view),
),
),
@@ -106,6 +101,8 @@ class DatabaseShareActionListState extends State {
onPointerDown: (_) => controller.show(),
child: RoundedTextButton(
title: LocaleKeys.shareAction_buttonText.tr(),
+ padding: const EdgeInsets.symmetric(horizontal: 12.0),
+ fontSize: 14.0,
textColor: Theme.of(context).colorScheme.onPrimary,
onPressed: () {},
),
diff --git a/frontend/appflowy_flutter/lib/plugins/document/document.dart b/frontend/appflowy_flutter/lib/plugins/document/document.dart
index 13242bbe0a9e8..bd9e4891d297c 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/document.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/document.dart
@@ -1,7 +1,5 @@
library document_plugin;
-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/document/application/document_appearance_cubit.dart';
@@ -22,6 +20,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.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_bloc/flutter_bloc.dart';
class DocumentPluginBuilder extends PluginBuilder {
@@ -130,7 +129,7 @@ class DocumentPluginWidgetBuilder extends PluginWidgetBuilder
}
@override
- Widget get leftBarItem => ViewTitleBar(view: view);
+ Widget get leftBarItem => ViewTitleBar(key: ValueKey(view.id), view: view);
@override
Widget tabBarItem(String pluginId) => ViewTabBarItem(view: notifier.view);
@@ -162,7 +161,7 @@ class DocumentPluginWidgetBuilder extends PluginWidgetBuilder
key: ValueKey('share_button_${view.id}'),
view: view,
),
- const HSpace(4),
+ const HSpace(10),
ViewFavoriteButton(
key: ValueKey('favorite_button_${view.id}'),
view: view,
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/banner.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/banner.dart
index 3936bf69684f6..e5fd6b6b8be17 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/banner.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/banner.dart
@@ -23,7 +23,7 @@ class DocumentBanner extends StatelessWidget {
constraints: const BoxConstraints(minHeight: 60),
child: Container(
width: double.infinity,
- color: colorScheme.surfaceVariant,
+ color: colorScheme.surfaceContainerHighest,
child: FittedBox(
fit: BoxFit.scaleDown,
child: Row(
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_configuration.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_configuration.dart
index 39cd608d0e01b..966a5685f5973 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_configuration.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_configuration.dart
@@ -5,6 +5,8 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/mo
import 'package:appflowy/plugins/document/presentation/editor_plugins/code_block/code_block_copy_button.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/custom_image_block_component.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
+import 'package:appflowy/plugins/document/presentation/editor_plugins/video/video_menu.dart';
+import 'package:appflowy/plugins/document/presentation/editor_plugins/video/video_placeholder.dart';
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
@@ -53,9 +55,8 @@ Map getEditorBuilderMap({
configuration: configuration.copyWith(
placeholderText: (_) => LocaleKeys.blockPlaceholders_todoList.tr(),
),
- iconBuilder: PlatformExtension.isMobile
- ? (_, node, onCheck) => TodoListIcon(node: node, onCheck: onCheck)
- : null,
+ iconBuilder: (_, node, onCheck) =>
+ TodoListIcon(node: node, onCheck: onCheck),
toggleChildrenTriggers: [
LogicalKeyboardKey.shift,
LogicalKeyboardKey.shiftLeft,
@@ -66,18 +67,14 @@ Map getEditorBuilderMap({
configuration: configuration.copyWith(
placeholderText: (_) => LocaleKeys.blockPlaceholders_bulletList.tr(),
),
- iconBuilder: PlatformExtension.isMobile
- ? (_, node) => BulletedListIcon(node: node)
- : null,
+ iconBuilder: (_, node) => BulletedListIcon(node: node),
),
NumberedListBlockKeys.type: NumberedListBlockComponentBuilder(
configuration: configuration.copyWith(
placeholderText: (_) => LocaleKeys.blockPlaceholders_numberList.tr(),
),
- iconBuilder: PlatformExtension.isMobile
- ? (_, node, textDirection) =>
- NumberedListIcon(node: node, textDirection: textDirection)
- : null,
+ iconBuilder: (_, node, textDirection) =>
+ NumberedListIcon(node: node, textDirection: textDirection),
),
QuoteBlockKeys.type: QuoteBlockComponentBuilder(
configuration: configuration.copyWith(
@@ -99,9 +96,13 @@ Map getEditorBuilderMap({
return const EdgeInsets.only(top: 12.0, bottom: 4.0);
},
- placeholderText: (node) => LocaleKeys.blockPlaceholders_heading.tr(
- args: [node.attributes[HeadingBlockKeys.level].toString()],
- ),
+ placeholderText: (node) {
+ int level = node.attributes[HeadingBlockKeys.level] ?? 6;
+ level = level.clamp(1, 6);
+ return LocaleKeys.blockPlaceholders_heading.tr(
+ args: [level.toString()],
+ );
+ },
),
textStyleBuilder: (level) => styleCustomizer.headingStyleBuilder(level),
),
@@ -110,7 +111,7 @@ Map getEditorBuilderMap({
showMenu: true,
menuBuilder: (Node node, CustomImageBlockComponentState state) =>
Positioned(
- top: 0,
+ top: 10,
right: 10,
child: ImageMenu(node: node, state: state),
),
@@ -163,7 +164,10 @@ Map getEditorBuilderMap({
),
),
CalloutBlockKeys.type: CalloutBlockComponentBuilder(
- configuration: configuration,
+ configuration: configuration.copyWith(
+ textStyle: (_) => styleCustomizer.calloutBlockStyleBuilder(),
+ placeholderTextStyle: (_) => styleCustomizer.calloutBlockStyleBuilder(),
+ ),
defaultColor: calloutBGColor,
),
DividerBlockKeys.type: DividerBlockComponentBuilder(
@@ -180,7 +184,6 @@ Map getEditorBuilderMap({
configuration: configuration,
),
CodeBlockKeys.type: CodeBlockComponentBuilder(
- editorState: editorState,
configuration: configuration.copyWith(
textStyle: (_) => styleCustomizer.codeBlockStyleBuilder(),
placeholderTextStyle: (_) => styleCustomizer.codeBlockStyleBuilder(),
@@ -228,6 +231,16 @@ Map getEditorBuilderMap({
errorBlockComponentBuilderKey: ErrorBlockComponentBuilder(
configuration: configuration,
),
+ VideoBlockKeys.type: VideoBlockComponentBuilder(
+ configuration: configuration,
+ showMenu: true,
+ menuBuilder: (node, state) => Positioned(
+ top: 10,
+ right: 10,
+ child: VideoMenu(node: node, state: state),
+ ),
+ placeholderBuilder: (node) => VideoPlaceholder(node: node),
+ ),
};
final builders = {
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 0b7a473176ac3..996aa4ced1ce0 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart
@@ -56,7 +56,18 @@ final List commandShortcutEvents = [
customPasteCommand,
customCutCommand,
...customTextAlignCommands,
- ...standardCommandShortcutEvents,
+
+ // remove standard shortcuts for copy, cut, paste, todo
+ ...standardCommandShortcutEvents
+ ..removeWhere(
+ (shortcut) => [
+ copyCommand,
+ cutCommand,
+ pasteCommand,
+ toggleTodoListCommand,
+ ].contains(shortcut),
+ ),
+
emojiShortcutEvent,
];
@@ -90,7 +101,6 @@ class AppFlowyEditorPage extends StatefulWidget {
final String Function(Node)? placeholderText;
/// Used to provide an initial selection on Page-load
- ///
final Selection? initialSelection;
final bool useViewInfoBloc;
@@ -111,15 +121,8 @@ class _AppFlowyEditorPageState extends State {
],
);
- late final List commandShortcutEvents = [
- toggleToggleListCommand,
- ...localizedCodeBlockCommands,
- customCopyCommand,
- customPasteCommand,
- customCutCommand,
- ...customTextAlignCommands,
- ...standardCommandShortcutEvents,
- emojiShortcutEvent,
+ late final List cmdShortcutEvents = [
+ ...commandShortcutEvents,
..._buildFindAndReplaceCommands(),
];
@@ -309,7 +312,7 @@ class _AppFlowyEditorPageState extends State {
),
// customize the shortcuts
characterShortcutEvents: characterShortcutEvents,
- commandShortcutEvents: commandShortcutEvents,
+ commandShortcutEvents: cmdShortcutEvents,
// customize the context menu items
contextMenuItems: customContextMenuItems,
// customize the header and footer.
@@ -385,6 +388,7 @@ class _AppFlowyEditorPageState extends State {
emojiMenuItem,
autoGeneratorMenuItem,
dateMenuItem,
+ videoBlockItem(LocaleKeys.document_plugins_video_label.tr()),
];
}
@@ -392,12 +396,6 @@ class _AppFlowyEditorPageState extends State {
if (widget.editorState.document.isEmpty) {
return (true, Selection.collapsed(Position(path: [0])));
}
- final nodes =
- widget.editorState.document.root.children.where((e) => e.delta != null);
- final isAllEmpty = nodes.isNotEmpty && nodes.every((e) => e.delta!.isEmpty);
- if (isAllEmpty) {
- return (true, Selection.collapsed(Position(path: nodes.first.path)));
- }
return const (false, null);
}
@@ -407,7 +405,7 @@ class _AppFlowyEditorPageState extends State {
final customizeShortcuts =
await settingsShortcutService.getCustomizeShortcuts();
await settingsShortcutService.updateCommandShortcuts(
- commandShortcutEvents,
+ cmdShortcutEvents,
customizeShortcuts,
);
}
@@ -437,7 +435,7 @@ class _AppFlowyEditorPageState extends State {
Material(
child: DecoratedBox(
decoration: BoxDecoration(
- color: Theme.of(context).colorScheme.surfaceVariant,
+ color: Theme.of(context).colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(4),
),
child: FindAndReplaceMenuWidget(
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/mobile_block_action_buttons.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/mobile_block_action_buttons.dart
index eb4487fa490f1..b80bb034a641e 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/mobile_block_action_buttons.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/mobile_block_action_buttons.dart
@@ -74,7 +74,6 @@ class MobileBlockActionButtons extends StatelessWidget {
context,
showHeader: true,
showCloseButton: true,
- showDivider: true,
showDragHandle: true,
title: LocaleKeys.document_plugins_action.tr(),
builder: (context) {
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option_action.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option_action.dart
index 09906e1429d36..d5e99e13f8fd3 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option_action.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option_action.dart
@@ -141,7 +141,7 @@ enum OptionDepthType {
class DividerOptionAction extends CustomActionCell {
@override
- Widget buildWithContext(BuildContext context) {
+ Widget buildWithContext(BuildContext context, PopoverController controller) {
return const Divider(
height: 1.0,
thickness: 1.0,
@@ -300,7 +300,7 @@ class ColorOptionAction extends PopoverActionCell {
colors: colors,
selected: selectedColor,
border: Border.all(
- color: Theme.of(context).colorScheme.onBackground,
+ color: AFThemeExtension.of(context).onBackground,
),
onTap: (option, index) async {
final transaction = editorState.transaction;
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart
index df78f6261efa2..2a1101794a6ec 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart
@@ -1,11 +1,10 @@
-import 'package:flutter/material.dart';
-
import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart';
import 'package:appflowy/plugins/base/icon/icon_picker.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/emoji_picker/emoji_picker.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class EmojiPickerButton extends StatelessWidget {
@@ -19,6 +18,7 @@ class EmojiPickerButton extends StatelessWidget {
this.offset,
this.direction,
this.title,
+ this.showBorder = true,
});
final String emoji;
@@ -30,6 +30,7 @@ class EmojiPickerButton extends StatelessWidget {
final Offset? offset;
final PopoverDirection? direction;
final String? title;
+ final bool showBorder;
@override
Widget build(BuildContext context) {
@@ -51,22 +52,28 @@ class EmojiPickerButton extends StatelessWidget {
onExit: () {},
),
),
- child: emoji.isEmpty && defaultIcon != null
- ? FlowyButton(
- useIntrinsicWidth: true,
- text: defaultIcon!,
- onTap: popoverController.show,
- )
- : FlowyTextButton(
- emoji,
- overflow: TextOverflow.visible,
- fontSize: emojiSize,
- padding: EdgeInsets.zero,
- constraints: const BoxConstraints(minWidth: 35.0),
- fillColor: Colors.transparent,
- mainAxisAlignment: MainAxisAlignment.center,
- onPressed: popoverController.show,
- ),
+ child: Container(
+ width: 30.0,
+ height: 30.0,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(8),
+ border: showBorder
+ ? Border.all(
+ color: Theme.of(context).dividerColor,
+ )
+ : null,
+ ),
+ child: FlowyButton(
+ margin: emoji.isEmpty && defaultIcon != null
+ ? EdgeInsets.zero
+ : const EdgeInsets.only(left: 2.0),
+ expandText: false,
+ text: emoji.isEmpty && defaultIcon != null
+ ? defaultIcon!
+ : FlowyText.emoji(emoji, fontSize: emojiSize),
+ onTap: popoverController.show,
+ ),
+ ),
);
}
return FlowyTextButton(
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/bulleted_list/bulleted_list_icon.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/bulleted_list/bulleted_list_icon.dart
index d0551b03af616..abb166ec12890 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/bulleted_list/bulleted_list_icon.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/bulleted_list/bulleted_list_icon.dart
@@ -37,7 +37,9 @@ class BulletedListIcon extends StatelessWidget {
@override
Widget build(BuildContext context) {
- final iconPadding = context.read().state.iconPadding;
+ final iconPadding = PlatformExtension.isMobile
+ ? context.read().state.iconPadding
+ : 0.0;
return Container(
constraints: const BoxConstraints(
minWidth: 22,
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_language_selector.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_language_selector.dart
index 323c15be1e297..97ac54e9f1930 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_language_selector.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_language_selector.dart
@@ -1,5 +1,3 @@
-import 'package:flutter/material.dart';
-
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/selectable_item_list_menu.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/string_extension.dart';
@@ -8,7 +6,9 @@ import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
CodeBlockLanguagePickerBuilder codeBlockLanguagePickerBuilder = (
@@ -70,7 +70,7 @@ class _CodeBlockLanguageSelectorState
widget.language?.capitalize() ??
LocaleKeys.document_codeBlock_language_auto.tr(),
constraints: const BoxConstraints(minWidth: 50),
- fontColor: Theme.of(context).colorScheme.onBackground,
+ fontColor: AFThemeExtension.of(context).onBackground,
padding: const EdgeInsets.symmetric(horizontal: 6.0, vertical: 4),
fillColor: Colors.transparent,
hoverColor: Theme.of(context).colorScheme.secondaryContainer,
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 741b3c16ee4a4..92f04719a4cec 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
@@ -1,12 +1,14 @@
import 'dart:io';
+import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
-import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart';
-import 'package:appflowy/plugins/base/icon/icon_picker.dart';
+import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/plugins/document/application/prelude.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/build_context_extension.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover_bloc.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart';
+import 'package:appflowy/plugins/document/presentation/editor_plugins/icon/icon_selector.dart';
+import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_icon_bloc.dart';
import 'package:appflowy/shared/appflowy_network_image.dart';
import 'package:appflowy/shared/flowy_gradient_colors.dart';
import 'package:appflowy/shared/google_fonts_extension.dart';
@@ -16,11 +18,13 @@ import 'package:appflowy/workspace/application/view/view_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
+import 'package:auto_size_text_field/auto_size_text_field.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:flowy_infra_ui/widget/ignore_parent_gesture.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:go_router/go_router.dart';
double kDocumentCoverHeight = 98.0;
double kDocumentTitlePadding = 20.0;
@@ -133,9 +137,11 @@ class _DocumentImmersiveCoverState extends State {
if (documentFontFamily != null && fontFamily != documentFontFamily) {
fontFamily = getGoogleFontSafely(documentFontFamily).fontFamily;
}
- return TextField(
+
+ return AutoSizeTextField(
controller: textEditingController,
focusNode: focusNode,
+ minFontSize: 18.0,
decoration: const InputDecoration(
border: InputBorder.none,
enabledBorder: InputBorder.none,
@@ -151,6 +157,7 @@ class _DocumentImmersiveCoverState extends State {
fontFamily: fontFamily,
color:
state.cover.isNone || state.cover.isPresets ? null : Colors.white,
+ overflow: TextOverflow.ellipsis,
),
onChanged: _rename,
onSubmitted: _rename,
@@ -167,12 +174,40 @@ class _DocumentImmersiveCoverState extends State {
),
),
onTap: () async {
- final result = await context.push(
- MobileEmojiPickerScreen.routeName,
+ final pageStyleIconBloc = PageStyleIconBloc(view: widget.view)
+ ..add(const PageStyleIconEvent.initial());
+ await showMobileBottomSheet(
+ context,
+ showDragHandle: true,
+ showDivider: false,
+ showDoneButton: true,
+ showHeader: true,
+ title: LocaleKeys.titleBar_pageIcon.tr(),
+ backgroundColor: AFThemeExtension.of(context).background,
+ enableDraggableScrollable: true,
+ minChildSize: 0.6,
+ initialChildSize: 0.61,
+ showRemoveButton: true,
+ onRemove: () {
+ pageStyleIconBloc.add(
+ const PageStyleIconEvent.updateIcon('', true),
+ );
+ },
+ scrollableWidgetBuilder: (_, controller) {
+ return BlocProvider.value(
+ value: pageStyleIconBloc,
+ child: Expanded(
+ child: Scrollbar(
+ controller: controller,
+ child: IconSelector(
+ scrollController: controller,
+ ),
+ ),
+ ),
+ );
+ },
+ builder: (_) => const SizedBox.shrink(),
);
- if (result != null && context.mounted) {
- context.read().add(ViewEvent.updateIcon(result.emoji));
- }
},
);
}
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/error/error_block_component_builder.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/error/error_block_component_builder.dart
index d16c035115b3c..ba6f01e9088d5 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/error/error_block_component_builder.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/error/error_block_component_builder.dart
@@ -61,7 +61,7 @@ class _ErrorBlockComponentWidgetState extends State
Widget build(BuildContext context) {
Widget child = DecoratedBox(
decoration: BoxDecoration(
- color: Theme.of(context).colorScheme.surfaceVariant,
+ color: Theme.of(context).colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(4),
),
child: FlowyButton(
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/find_and_replace/find_and_replace_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/find_and_replace/find_and_replace_menu.dart
index ce756b9ffd538..7c84f2c31b34c 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/find_and_replace/find_and_replace_menu.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/find_and_replace/find_and_replace_menu.dart
@@ -2,7 +2,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
-import 'package:flowy_infra_ui/flowy_infra_ui.dart' hide WidgetBuilder;
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/text_input.dart';
import 'package:flutter/material.dart';
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart
index 7a606f33ef930..3a22909744287 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart
@@ -300,9 +300,10 @@ class _DocumentHeaderToolbarState extends State {
: (CoverType.color, '0xffe8e0ff'),
),
useIntrinsicWidth: true,
- leftIcon: const FlowySvg(FlowySvgs.image_s),
+ leftIcon: const FlowySvg(FlowySvgs.add_cover_s),
text: FlowyText.small(
LocaleKeys.document_plugins_cover_addCover.tr(),
+ color: Theme.of(context).hintColor,
),
),
);
@@ -311,28 +312,24 @@ class _DocumentHeaderToolbarState extends State {
if (widget.hasIcon) {
children.add(
FlowyButton(
- leftIconSize: const Size.square(18),
onTap: () => widget.onIconOrCoverChanged(icon: ""),
useIntrinsicWidth: true,
- leftIcon: const Icon(
- Icons.emoji_emotions_outlined,
- size: 18,
- ),
+ leftIcon: const FlowySvg(FlowySvgs.add_icon_s),
+ iconPadding: 4.0,
text: FlowyText.small(
LocaleKeys.document_plugins_cover_removeIcon.tr(),
+ color: Theme.of(context).hintColor,
),
),
);
} else {
Widget child = FlowyButton(
- leftIconSize: const Size.square(18),
useIntrinsicWidth: true,
- leftIcon: const Icon(
- Icons.emoji_emotions_outlined,
- size: 18,
- ),
+ leftIcon: const FlowySvg(FlowySvgs.add_icon_s),
+ iconPadding: 4.0,
text: FlowyText.small(
LocaleKeys.document_plugins_cover_addIcon.tr(),
+ color: Theme.of(context).hintColor,
),
onTap: PlatformExtension.isDesktop
? null
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/icon/icon_selector.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/icon/icon_selector.dart
new file mode 100644
index 0000000000000..5da4528a3c382
--- /dev/null
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/icon/icon_selector.dart
@@ -0,0 +1,159 @@
+import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_search_text_field.dart';
+import 'package:appflowy/plugins/base/emoji/emoji_picker.dart';
+import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_icon_bloc.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:flutter_emoji_mart/flutter_emoji_mart.dart';
+
+class IconSelector extends StatefulWidget {
+ const IconSelector({
+ super.key,
+ required this.scrollController,
+ });
+
+ final ScrollController scrollController;
+
+ @override
+ State createState() => _IconSelectorState();
+}
+
+class _IconSelectorState extends State {
+ EmojiData? emojiData;
+ List availableEmojis = [];
+
+ PageStyleIconBloc? pageStyleIconBloc;
+
+ @override
+ void initState() {
+ super.initState();
+
+ // load the emoji data from cache if it's available
+ if (kCachedEmojiData != null) {
+ emojiData = kCachedEmojiData;
+ availableEmojis = _setupAvailableEmojis(emojiData!);
+ } else {
+ EmojiData.builtIn().then(
+ (value) {
+ kCachedEmojiData = value;
+ setState(() {
+ emojiData = value;
+ availableEmojis = _setupAvailableEmojis(value);
+ });
+ },
+ );
+ }
+
+ pageStyleIconBloc = context.read();
+ }
+
+ @override
+ void dispose() {
+ pageStyleIconBloc?.close();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ if (emojiData == null) {
+ return const Center(child: CircularProgressIndicator());
+ }
+
+ return RepaintBoundary(
+ child: BlocBuilder(
+ builder: (_, state) => Column(
+ children: [
+ _buildSearchBar(context),
+ Expanded(
+ child: GridView.count(
+ crossAxisCount: 7,
+ controller: widget.scrollController,
+ children: [
+ for (final emoji in availableEmojis)
+ _buildEmoji(context, emoji, state.icon),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ Widget _buildEmoji(
+ BuildContext context,
+ String emoji,
+ String? selectedEmoji,
+ ) {
+ Widget child = SizedBox.square(
+ dimension: 24.0,
+ child: Center(
+ child: FlowyText.emoji(
+ emoji,
+ fontSize: 24,
+ ),
+ ),
+ );
+
+ if (emoji == selectedEmoji) {
+ child = Center(
+ child: Container(
+ width: 40,
+ height: 40,
+ decoration: ShapeDecoration(
+ shape: RoundedRectangleBorder(
+ side: const BorderSide(
+ width: 1.40,
+ strokeAlign: BorderSide.strokeAlignOutside,
+ color: Color(0xFF00BCF0),
+ ),
+ borderRadius: BorderRadius.circular(10),
+ ),
+ ),
+ child: child,
+ ),
+ );
+ }
+
+ return GestureDetector(
+ onTap: () {
+ context.read().add(
+ PageStyleIconEvent.updateIcon(emoji, true),
+ );
+ },
+ child: child,
+ );
+ }
+
+ List _setupAvailableEmojis(EmojiData emojiData) {
+ final categories = emojiData.categories;
+ availableEmojis = categories
+ .map((e) => e.emojiIds.map((e) => emojiData.getEmojiById(e)))
+ .expand((e) => e)
+ .toList();
+ return availableEmojis;
+ }
+
+ Widget _buildSearchBar(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(
+ vertical: 8.0,
+ horizontal: 12.0,
+ ),
+ child: FlowyMobileSearchTextField(
+ onChanged: (keyword) {
+ if (emojiData == null) {
+ return;
+ }
+
+ final filtered = emojiData!.filterByKeyword(keyword);
+ final availableEmojis = _setupAvailableEmojis(filtered);
+
+ setState(() {
+ this.availableEmojis = availableEmojis;
+ });
+ },
+ ),
+ );
+ }
+}
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/custom_image_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/custom_image_block_component.dart
index c389977333061..722a79c2f549a 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/custom_image_block_component.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/custom_image_block_component.dart
@@ -1,5 +1,7 @@
import 'dart:io';
+import 'package:flutter/material.dart';
+
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
@@ -13,7 +15,6 @@ import 'package:appflowy/util/string_extension.dart';
import 'package:appflowy/workspace/presentation/home/toast.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide ResizableImage;
import 'package:easy_localization/easy_localization.dart';
-import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:string_validator/string_validator.dart';
@@ -109,10 +110,7 @@ class CustomImageBlockComponentBuilder extends BlockComponentBuilder {
node: node,
showActions: showActions(node),
configuration: configuration,
- actionBuilder: (context, state) => actionBuilder(
- blockComponentContext,
- state,
- ),
+ actionBuilder: (_, state) => actionBuilder(blockComponentContext, state),
showMenu: showMenu,
menuBuilder: menuBuilder,
);
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_placeholder.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_placeholder.dart
index f193f91617698..5d51c1b390e7e 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_placeholder.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_placeholder.dart
@@ -49,7 +49,7 @@ class ImagePlaceholderState extends State {
Widget build(BuildContext context) {
final Widget child = DecoratedBox(
decoration: BoxDecoration(
- color: Theme.of(context).colorScheme.surfaceVariant,
+ color: Theme.of(context).colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(4),
),
child: FlowyHover(
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/unsplash_image_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/unsplash_image_widget.dart
index 36d90ac6fb0b2..eda320bdb399d 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/unsplash_image_widget.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/unsplash_image_widget.dart
@@ -112,7 +112,7 @@ class _UnsplashImageWidgetState extends State {
}
}
-class _UnsplashImages extends StatelessWidget {
+class _UnsplashImages extends StatefulWidget {
const _UnsplashImages({
required this.type,
required this.photos,
@@ -123,17 +123,24 @@ class _UnsplashImages extends StatelessWidget {
final List photos;
final OnSelectUnsplashImage onSelectUnsplashImage;
+ @override
+ State<_UnsplashImages> createState() => _UnsplashImagesState();
+}
+
+class _UnsplashImagesState extends State<_UnsplashImages> {
+ int _selectedPhotoIndex = -1;
+
@override
Widget build(BuildContext context) {
- final crossAxisCount = switch (type) {
+ final crossAxisCount = switch (widget.type) {
UnsplashImageType.halfScreen => 3,
UnsplashImageType.fullScreen => 2,
};
- final mainAxisSpacing = switch (type) {
+ final mainAxisSpacing = switch (widget.type) {
UnsplashImageType.halfScreen => 16.0,
UnsplashImageType.fullScreen => 16.0,
};
- final crossAxisSpacing = switch (type) {
+ final crossAxisSpacing = switch (widget.type) {
UnsplashImageType.halfScreen => 10.0,
UnsplashImageType.fullScreen => 16.0,
};
@@ -142,17 +149,23 @@ class _UnsplashImages extends StatelessWidget {
mainAxisSpacing: mainAxisSpacing,
crossAxisSpacing: crossAxisSpacing,
childAspectRatio: 4 / 3,
- children: photos
- .map(
- (photo) => _UnsplashImage(
- type: type,
- photo: photo,
- onTap: () => onSelectUnsplashImage(
- photo.urls.regular.toString(),
- ),
- ),
- )
- .toList(),
+ children: widget.photos.asMap().entries.map((entry) {
+ final index = entry.key;
+ final photo = entry.value;
+ return _UnsplashImage(
+ type: widget.type,
+ photo: photo,
+ onTap: () {
+ widget.onSelectUnsplashImage(
+ photo.urls.regular.toString(),
+ );
+ setState(() {
+ _selectedPhotoIndex = index;
+ });
+ },
+ isSelected: index == _selectedPhotoIndex,
+ );
+ }).toList(),
);
}
}
@@ -162,11 +175,13 @@ class _UnsplashImage extends StatelessWidget {
required this.type,
required this.photo,
required this.onTap,
+ required this.isSelected,
});
final UnsplashImageType type;
final Photo photo;
final VoidCallback onTap;
+ final bool isSelected;
@override
Widget build(BuildContext context) {
@@ -177,7 +192,19 @@ class _UnsplashImage extends StatelessWidget {
return GestureDetector(
onTap: onTap,
- child: child,
+ child: isSelected
+ ? Container(
+ clipBehavior: Clip.antiAlias,
+ decoration: ShapeDecoration(
+ shape: RoundedRectangleBorder(
+ side: const BorderSide(width: 1.50, color: Color(0xFF00BCF0)),
+ borderRadius: BorderRadius.circular(8.0),
+ ),
+ ),
+ padding: const EdgeInsets.all(2.0),
+ child: child,
+ )
+ : child,
);
}
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/unsupport_image_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/unsupport_image_widget.dart
index 3403a1ff31b54..017e5a94b287d 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/unsupport_image_widget.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/unsupport_image_widget.dart
@@ -14,7 +14,7 @@ class UnSupportImageWidget extends StatelessWidget {
Widget build(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(
- color: Theme.of(context).colorScheme.surfaceVariant,
+ color: Theme.of(context).colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(4),
),
child: FlowyHover(
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu.dart
index 4c9de6b07d404..0679f879962a8 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu.dart
@@ -107,7 +107,7 @@ class _UploadImageMenuState extends State {
}),
indicatorSize: TabBarIndicatorSize.label,
isScrollable: true,
- overlayColor: MaterialStatePropertyAll(
+ overlayColor: WidgetStatePropertyAll(
PlatformExtension.isDesktop
? Theme.of(context).colorScheme.secondary
: Colors.transparent,
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/inline_math_equation/inline_math_equation.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/inline_math_equation/inline_math_equation.dart
index 543cee120757b..7ce143acba4d3 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/inline_math_equation/inline_math_equation.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/inline_math_equation/inline_math_equation.dart
@@ -77,7 +77,7 @@ class _InlineMathEquationState extends State {
),
fontSize: 14.0,
color: widget.textStyle?.color ??
- theme.colorScheme.onBackground,
+ theme.colorScheme.onSurface,
),
),
const HSpace(2),
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/math_equation/math_equation_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/math_equation/math_equation_block_component.dart
index 56a9739531fa5..c7d298ff09add 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/math_equation/math_equation_block_component.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/math_equation/math_equation_block_component.dart
@@ -124,7 +124,7 @@ class MathEquationBlockComponentWidgetState
decoration: BoxDecoration(
color: formula.isNotEmpty
? Colors.transparent
- : Theme.of(context).colorScheme.surfaceVariant,
+ : Theme.of(context).colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(4),
),
child: FlowyHover(
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/utils.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/utils.dart
index 21f3b7ea68bd4..ad4d52381287b 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/utils.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/utils.dart
@@ -10,8 +10,6 @@ Future showEditLinkBottomSheet(
) {
return showMobileBottomSheet(
context,
- showHeader: false,
- showCloseButton: false,
showDragHandle: true,
padding: const EdgeInsets.symmetric(horizontal: 16),
builder: (context) {
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/aa_menu/_color_list.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/aa_menu/_color_list.dart
index 2de13ed4590e2..438aa1264b6d9 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/aa_menu/_color_list.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/aa_menu/_color_list.dart
@@ -22,8 +22,6 @@ Future showTextColorAndBackgroundColorPicker(
await showMobileBottomSheet(
context,
showHeader: true,
- showCloseButton: false,
- showDivider: true,
showDragHandle: true,
showDoneButton: true,
barrierColor: Colors.transparent,
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/add_block_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/add_block_toolbar_item.dart
index d0be5af466aee..7ce86c3035747 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/add_block_toolbar_item.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/add_block_toolbar_item.dart
@@ -197,6 +197,15 @@ class _AddBlockMenu extends StatelessWidget {
},
),
+ // video
+ TypeOptionMenuItemValue(
+ value: VideoBlockKeys.type,
+ backgroundColor: colorMap[VideoBlockKeys.type]!,
+ text: LocaleKeys.document_plugins_video_label.tr(),
+ icon: FlowySvgs.m_add_block_video_s,
+ onTap: (_, __) => _insertBlock(videoBlockNode()),
+ ),
+
// date
TypeOptionMenuItemValue(
value: ParagraphBlockKeys.type,
@@ -287,6 +296,7 @@ class _AddBlockMenu extends StatelessWidget {
NumberedListBlockKeys.type: const Color(0xFFA35F94),
ToggleListBlockKeys.type: const Color(0xFFA35F94),
ImageBlockKeys.type: const Color(0xFFBAAC74),
+ VideoBlockKeys.type: const Color(0xFFBAAC74),
MentionBlockKeys.type: const Color(0xFF40AAB8),
DividerBlockKeys.type: const Color(0xFF4BB299),
CalloutBlockKeys.type: const Color(0xFF66599B),
@@ -303,6 +313,7 @@ class _AddBlockMenu extends StatelessWidget {
NumberedListBlockKeys.type: const Color(0xFFFFB9EF),
ToggleListBlockKeys.type: const Color(0xFFFFB9EF),
ImageBlockKeys.type: const Color(0xFFFDEDA7),
+ VideoBlockKeys.type: const Color(0xFFFDEDA7),
MentionBlockKeys.type: const Color(0xFF91EAF5),
DividerBlockKeys.type: const Color(0xFF98F4CD),
CalloutBlockKeys.type: const Color(0xFFCABDFF),
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_cover_image.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_cover_image.dart
index 214fb7456f763..fd0aa86fa7df7 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_cover_image.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_cover_image.dart
@@ -20,6 +20,7 @@ import 'package:appflowy_backend/log.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/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/snap_bar.dart';
import 'package:flutter/material.dart';
@@ -188,7 +189,7 @@ class PageStyleCoverImage extends StatelessWidget {
);
},
title: LocaleKeys.pageStyle_presets.tr(),
- backgroundColor: Theme.of(context).colorScheme.background,
+ backgroundColor: AFThemeExtension.of(context).background,
builder: (_) {
return BlocProvider.value(
value: pageStyleBloc,
@@ -256,6 +257,9 @@ class PageStyleCoverImage extends StatelessWidget {
void _showUnsplash(BuildContext context) {
final pageStyleBloc = context.read();
+ final backgroundColor = AFThemeExtension.of(context).background;
+ final maxHeight = MediaQuery.of(context).size.height * 0.6;
+
context.pop();
showMobileBottomSheet(
@@ -266,7 +270,7 @@ class PageStyleCoverImage extends StatelessWidget {
showHeader: true,
showRemoveButton: true,
title: LocaleKeys.pageStyle_unsplash.tr(),
- backgroundColor: Theme.of(context).colorScheme.background,
+ backgroundColor: backgroundColor,
onRemove: () {
pageStyleBloc.add(
DocumentPageStyleEvent.updateCoverImage(
@@ -277,11 +281,11 @@ class PageStyleCoverImage extends StatelessWidget {
builder: (_) {
return ConstrainedBox(
constraints: BoxConstraints(
- maxHeight: MediaQuery.of(context).size.height * 0.6,
+ maxHeight: maxHeight,
minHeight: 80,
),
child: BlocProvider.value(
- value: context.read(),
+ value: pageStyleBloc,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: UnsplashImageWidget(
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_icon.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_icon.dart
index a3ccc974f7de6..3f3ed875228dd 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_icon.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_icon.dart
@@ -1,16 +1,15 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
-import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_search_text_field.dart';
-import 'package:appflowy/plugins/base/emoji/emoji_picker.dart';
+import 'package:appflowy/plugins/document/presentation/editor_plugins/icon/icon_selector.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_icon_bloc.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_util.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:flutter_emoji_mart/flutter_emoji_mart.dart';
import 'package:go_router/go_router.dart';
class PageStyleIcon extends StatefulWidget {
@@ -77,8 +76,7 @@ class _PageStyleIconState extends State {
showDoneButton: true,
showHeader: true,
title: LocaleKeys.titleBar_pageIcon.tr(),
- backgroundColor: Theme.of(context).colorScheme.background,
- isScrollControlled: true,
+ backgroundColor: AFThemeExtension.of(context).background,
enableDraggableScrollable: true,
minChildSize: 0.6,
initialChildSize: 0.61,
@@ -94,7 +92,7 @@ class _PageStyleIconState extends State {
child: Expanded(
child: Scrollbar(
controller: controller,
- child: _IconSelector(
+ child: IconSelector(
scrollController: controller,
),
),
@@ -105,154 +103,3 @@ class _PageStyleIconState extends State {
);
}
}
-
-class _IconSelector extends StatefulWidget {
- const _IconSelector({
- required this.scrollController,
- });
-
- final ScrollController scrollController;
-
- @override
- State<_IconSelector> createState() => _IconSelectorState();
-}
-
-class _IconSelectorState extends State<_IconSelector> {
- EmojiData? emojiData;
- List availableEmojis = [];
-
- PageStyleIconBloc? pageStyleIconBloc;
-
- @override
- void initState() {
- super.initState();
-
- // load the emoji data from cache if it's available
- if (kCachedEmojiData != null) {
- emojiData = kCachedEmojiData;
- availableEmojis = _setupAvailableEmojis(emojiData!);
- } else {
- EmojiData.builtIn().then(
- (value) {
- kCachedEmojiData = value;
- setState(() {
- emojiData = value;
- availableEmojis = _setupAvailableEmojis(value);
- });
- },
- );
- }
-
- pageStyleIconBloc = context.read();
- }
-
- @override
- void dispose() {
- pageStyleIconBloc?.close();
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- if (emojiData == null) {
- return const Center(child: CircularProgressIndicator());
- }
-
- return RepaintBoundary(
- child: BlocBuilder(
- builder: (_, state) => Column(
- children: [
- _buildSearchBar(context),
- Expanded(
- child: GridView.count(
- crossAxisCount: 7,
- controller: widget.scrollController,
- children: [
- for (final emoji in availableEmojis)
- _buildEmoji(context, emoji, state.icon),
- ],
- ),
- ),
- ],
- ),
- ),
- );
- }
-
- Widget _buildEmoji(
- BuildContext context,
- String emoji,
- String? selectedEmoji,
- ) {
- Widget child = SizedBox.square(
- dimension: 24.0,
- child: Center(
- child: FlowyText.emoji(
- emoji,
- fontSize: 24,
- ),
- ),
- );
-
- if (emoji == selectedEmoji) {
- child = Center(
- child: Container(
- width: 40,
- height: 40,
- decoration: ShapeDecoration(
- shape: RoundedRectangleBorder(
- side: const BorderSide(
- width: 1.40,
- strokeAlign: BorderSide.strokeAlignOutside,
- color: Color(0xFF00BCF0),
- ),
- borderRadius: BorderRadius.circular(10),
- ),
- ),
- child: child,
- ),
- );
- }
-
- return GestureDetector(
- onTap: () {
- context.read().add(
- PageStyleIconEvent.updateIcon(emoji, true),
- );
- },
- child: child,
- );
- }
-
- List _setupAvailableEmojis(EmojiData emojiData) {
- final categories = emojiData.categories;
- availableEmojis = categories
- .map((e) => e.emojiIds.map((e) => emojiData.getEmojiById(e)))
- .expand((e) => e)
- .toList();
- return availableEmojis;
- }
-
- Widget _buildSearchBar(BuildContext context) {
- return Padding(
- padding: const EdgeInsets.symmetric(
- vertical: 8.0,
- horizontal: 12.0,
- ),
- child: FlowyMobileSearchTextField(
- onChanged: (keyword) {
- if (emojiData == null) {
- return;
- }
-
- final filtered = emojiData!.filterByKeyword(keyword);
- final availableEmojis = _setupAvailableEmojis(filtered);
-
- setState(() {
- this.availableEmojis = availableEmojis;
- });
- },
- ),
- );
- }
-}
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_layout.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_layout.dart
index 054f544c8f4ac..211e287d159c2 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_layout.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_layout.dart
@@ -8,6 +8,7 @@ import 'package:appflowy/shared/feedback_gesture_detector.dart';
import 'package:appflowy/util/font_family_extension.dart';
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -208,8 +209,7 @@ class _FontButton extends StatelessWidget {
showDoneButton: true,
showHeader: true,
title: LocaleKeys.titleBar_font.tr(),
- backgroundColor: Theme.of(context).colorScheme.background,
- isScrollControlled: true,
+ backgroundColor: AFThemeExtension.of(context).background,
enableDraggableScrollable: true,
minChildSize: 0.6,
initialChildSize: 0.61,
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/table/table_option_action.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/table/table_option_action.dart
index afb2b63f49f07..6d0597319c470 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/table/table_option_action.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/table/table_option_action.dart
@@ -137,7 +137,7 @@ class TableColorOptionAction extends PopoverActionCell {
colors: colors,
selected: selectedColor,
border: Border.all(
- color: Theme.of(context).colorScheme.onBackground,
+ color: AFThemeExtension.of(context).onBackground,
),
onTap: (option, index) async {
final backgroundColor =
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/todo_list/todo_list_icon.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/todo_list/todo_list_icon.dart
index deb45c2182599..85972a3c2ce71 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/todo_list/todo_list_icon.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/todo_list/todo_list_icon.dart
@@ -17,7 +17,9 @@ class TodoListIcon extends StatelessWidget {
@override
Widget build(BuildContext context) {
- final iconPadding = context.read().state.iconPadding;
+ final iconPadding = PlatformExtension.isMobile
+ ? context.read().state.iconPadding
+ : 0.0;
final checked = node.attributes[TodoListBlockKeys.checked] ?? false;
return GestureDetector(
behavior: HitTestBehavior.opaque,
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/video/upload_video_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/video/upload_video_menu.dart
new file mode 100644
index 0000000000000..2b2752e3f5a3f
--- /dev/null
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/video/upload_video_menu.dart
@@ -0,0 +1,93 @@
+import 'package:flutter/material.dart';
+
+import 'package:appflowy/generated/locale_keys.g.dart';
+import 'package:appflowy/shared/patterns/common_patterns.dart';
+import 'package:appflowy_editor/appflowy_editor.dart' hide ColorOption;
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+
+class UploadVideoMenu extends StatefulWidget {
+ const UploadVideoMenu({
+ super.key,
+ required this.onUrlSubmitted,
+ this.onSelectedColor,
+ });
+
+ final void Function(String url) onUrlSubmitted;
+ final void Function(String color)? onSelectedColor;
+
+ @override
+ State createState() => _UploadVideoMenuState();
+}
+
+class _UploadVideoMenuState extends State {
+ @override
+ Widget build(BuildContext context) {
+ final constraints =
+ PlatformExtension.isMobile ? const BoxConstraints(minHeight: 92) : null;
+
+ return Container(
+ padding: const EdgeInsets.all(8.0),
+ constraints: constraints,
+ child: _EmbedUrl(onSubmit: widget.onUrlSubmitted),
+ );
+ }
+}
+
+class _EmbedUrl extends StatefulWidget {
+ const _EmbedUrl({required this.onSubmit});
+
+ final void Function(String url) onSubmit;
+
+ @override
+ State<_EmbedUrl> createState() => _EmbedUrlState();
+}
+
+class _EmbedUrlState extends State<_EmbedUrl> {
+ bool isUrlValid = true;
+ String inputText = '';
+
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ FlowyTextField(
+ hintText: LocaleKeys.document_plugins_video_placeholder.tr(),
+ onChanged: (value) => inputText = value,
+ onEditingComplete: submit,
+ ),
+ if (!isUrlValid) ...[
+ const VSpace(8),
+ FlowyText(
+ LocaleKeys.document_plugins_video_invalidVideoUrl.tr(),
+ color: Theme.of(context).colorScheme.error,
+ ),
+ ],
+ const VSpace(8),
+ SizedBox(
+ width: 160,
+ child: FlowyButton(
+ showDefaultBoxDecorationOnMobile: true,
+ margin: const EdgeInsets.all(8.0),
+ text: FlowyText(
+ LocaleKeys.document_plugins_video_insertVideo.tr(),
+ textAlign: TextAlign.center,
+ ),
+ onTap: submit,
+ ),
+ ),
+ ],
+ );
+ }
+
+ void submit() {
+ if (checkUrlValidity(inputText)) {
+ return widget.onSubmit(inputText);
+ }
+
+ setState(() => isUrlValid = false);
+ }
+
+ bool checkUrlValidity(String url) => videoUrlRegex.hasMatch(url);
+}
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/video/video_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/video/video_menu.dart
new file mode 100644
index 0000000000000..a82156586e354
--- /dev/null
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/video/video_menu.dart
@@ -0,0 +1,314 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+
+import 'package:appflowy/generated/flowy_svgs.g.dart';
+import 'package:appflowy/generated/locale_keys.g.dart';
+import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_block_action_widget.dart';
+import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart';
+import 'package:appflowy/mobile/presentation/widgets/flowy_option_tile.dart';
+import 'package:appflowy/plugins/document/presentation/editor_plugins/align_toolbar_item/align_toolbar_item.dart';
+import 'package:appflowy/plugins/document/presentation/editor_plugins/block_menu/block_menu_button.dart';
+import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
+import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
+import 'package:appflowy/startup/startup.dart';
+import 'package:appflowy/workspace/presentation/home/toast.dart';
+import 'package:appflowy_editor/appflowy_editor.dart';
+import 'package:appflowy_editor_plugins/appflowy_editor_plugins.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:flowy_infra_ui/widget/ignore_parent_gesture.dart';
+import 'package:go_router/go_router.dart';
+import 'package:provider/provider.dart';
+
+class VideoMenu extends StatefulWidget {
+ const VideoMenu({
+ super.key,
+ required this.node,
+ required this.state,
+ });
+
+ final Node node;
+ final VideoBlockComponentState state;
+
+ @override
+ State createState() => _VideoMenuState();
+}
+
+class _VideoMenuState extends State {
+ late final String? url = widget.node.attributes[VideoBlockKeys.url];
+
+ @override
+ Widget build(BuildContext context) {
+ final theme = Theme.of(context);
+ return Container(
+ height: 32,
+ decoration: BoxDecoration(
+ color: theme.cardColor,
+ borderRadius: BorderRadius.circular(4.0),
+ boxShadow: [
+ BoxShadow(
+ blurRadius: 5,
+ spreadRadius: 1,
+ color: Colors.black.withOpacity(0.1),
+ ),
+ ],
+ ),
+ child: PlatformExtension.isMobile
+ ? MenuBlockButton(
+ tooltip: LocaleKeys.button_edit.tr(),
+ iconData: FlowySvgs.more_s,
+ onTap: showMobileMenu,
+ )
+ : Row(
+ children: [
+ const HSpace(4),
+ MenuBlockButton(
+ tooltip: LocaleKeys.editor_copyLink.tr(),
+ iconData: FlowySvgs.copy_s,
+ onTap: copyVideoLink,
+ ),
+ const HSpace(4),
+ _VideoAlignButton(
+ node: widget.node,
+ state: widget.state,
+ ),
+ const _Divider(),
+ MenuBlockButton(
+ tooltip: LocaleKeys.button_delete.tr(),
+ iconData: FlowySvgs.delete_s,
+ onTap: deleteVideo,
+ ),
+ const HSpace(4),
+ ],
+ ),
+ );
+ }
+
+ void copyVideoLink() {
+ if (url != null) {
+ Clipboard.setData(ClipboardData(text: url!));
+ showSnackBarMessage(
+ context,
+ LocaleKeys.document_plugins_video_copiedToPasteBoard.tr(),
+ );
+ }
+ }
+
+ void showMobileMenu() {
+ final editorState = context.read()
+ ..updateSelectionWithReason(null, extraInfo: {});
+ final src = widget.node.attributes[VideoBlockKeys.url];
+ showMobileBottomSheet(
+ context,
+ showHeader: true,
+ showCloseButton: true,
+ showDragHandle: true,
+ title: LocaleKeys.document_plugins_action.tr(),
+ builder: (context) {
+ return BlockActionBottomSheet(
+ extendActionWidgets: [
+ FlowyOptionTile.text(
+ showTopBorder: false,
+ text: LocaleKeys.editor_copyLink.tr(),
+ leftIcon: const FlowySvg(
+ FlowySvgs.m_field_copy_s,
+ ),
+ onTap: () async {
+ context.pop();
+ showSnackBarMessage(
+ context,
+ LocaleKeys.document_plugins_video_copiedToPasteBoard.tr(),
+ );
+ await getIt().setPlainText(src);
+ },
+ ),
+ ],
+ onAction: (action) async {
+ context.pop();
+
+ final transaction = editorState.transaction;
+ switch (action) {
+ case BlockActionBottomSheetType.delete:
+ transaction.deleteNode(widget.node);
+ break;
+ case BlockActionBottomSheetType.duplicate:
+ transaction.insertNode(
+ widget.node.path.next,
+ widget.node.copyWith(),
+ );
+ break;
+ case BlockActionBottomSheetType.insertAbove:
+ case BlockActionBottomSheetType.insertBelow:
+ final path = action == BlockActionBottomSheetType.insertAbove
+ ? widget.node.path
+ : widget.node.path.next;
+ transaction
+ ..insertNode(path, paragraphNode())
+ ..afterSelection = Selection.collapsed(Position(path: path));
+ break;
+ default:
+ }
+
+ if (transaction.operations.isNotEmpty) {
+ await editorState.apply(transaction);
+ }
+ },
+ );
+ },
+ );
+ }
+
+ Future deleteVideo() async {
+ final node = widget.node;
+ final editorState = context.read();
+ final transaction = editorState.transaction;
+ transaction.deleteNode(node);
+ transaction.afterSelection = null;
+ await editorState.apply(transaction);
+ }
+}
+
+class _VideoAlignButton extends StatefulWidget {
+ const _VideoAlignButton({
+ required this.node,
+ required this.state,
+ });
+
+ final Node node;
+ final VideoBlockComponentState state;
+
+ @override
+ State<_VideoAlignButton> createState() => _VideoAlignButtonState();
+}
+
+const interceptorKey = 'video-align';
+
+class _VideoAlignButtonState extends State<_VideoAlignButton> {
+ final gestureInterceptor = SelectionGestureInterceptor(
+ key: interceptorKey,
+ canTap: (_) => false,
+ );
+
+ String get align =>
+ widget.node.attributes[VideoBlockKeys.alignment] ?? centerAlignmentKey;
+ final popoverController = PopoverController();
+ late final EditorState editorState;
+
+ @override
+ void initState() {
+ super.initState();
+ editorState = context.read();
+ }
+
+ @override
+ void dispose() {
+ allowMenuClose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return IgnoreParentGestureWidget(
+ child: AppFlowyPopover(
+ onClose: allowMenuClose,
+ controller: popoverController,
+ windowPadding: const EdgeInsets.all(0),
+ margin: const EdgeInsets.all(0),
+ direction: PopoverDirection.bottomWithCenterAligned,
+ offset: const Offset(0, 10),
+ child: MenuBlockButton(
+ tooltip: LocaleKeys.document_plugins_optionAction_align.tr(),
+ iconData: iconFor(align),
+ ),
+ popupBuilder: (_) {
+ preventMenuClose();
+ return _AlignButtons(onAlignChanged: onAlignChanged);
+ },
+ ),
+ );
+ }
+
+ void onAlignChanged(String align) {
+ popoverController.close();
+
+ final transaction = editorState.transaction;
+ transaction.updateNode(widget.node, {VideoBlockKeys.alignment: align});
+ editorState.apply(transaction);
+
+ allowMenuClose();
+ }
+
+ void preventMenuClose() {
+ widget.state.preventClose = true;
+ editorState.service.selectionService
+ .registerGestureInterceptor(gestureInterceptor);
+ }
+
+ void allowMenuClose() {
+ widget.state.preventClose = false;
+ editorState.service.selectionService
+ .unregisterGestureInterceptor(interceptorKey);
+ }
+
+ FlowySvgData iconFor(String alignment) {
+ switch (alignment) {
+ case leftAlignmentKey:
+ return FlowySvgs.align_left_s;
+ case rightAlignmentKey:
+ return FlowySvgs.align_right_s;
+ case centerAlignmentKey:
+ default:
+ return FlowySvgs.align_center_s;
+ }
+ }
+}
+
+class _AlignButtons extends StatelessWidget {
+ const _AlignButtons({required this.onAlignChanged});
+
+ final Function(String align) onAlignChanged;
+
+ @override
+ Widget build(BuildContext context) {
+ return SizedBox(
+ height: 32,
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ const HSpace(4),
+ MenuBlockButton(
+ tooltip: LocaleKeys.document_plugins_optionAction_left,
+ iconData: FlowySvgs.align_left_s,
+ onTap: () => onAlignChanged(leftAlignmentKey),
+ ),
+ const _Divider(),
+ MenuBlockButton(
+ tooltip: LocaleKeys.document_plugins_optionAction_center,
+ iconData: FlowySvgs.align_center_s,
+ onTap: () => onAlignChanged(centerAlignmentKey),
+ ),
+ const _Divider(),
+ MenuBlockButton(
+ tooltip: LocaleKeys.document_plugins_optionAction_right,
+ iconData: FlowySvgs.align_right_s,
+ onTap: () => onAlignChanged(rightAlignmentKey),
+ ),
+ const HSpace(4),
+ ],
+ ),
+ );
+ }
+}
+
+class _Divider extends StatelessWidget {
+ const _Divider();
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.all(8),
+ child: Container(width: 1, color: Colors.grey),
+ );
+ }
+}
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/video/video_placeholder.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/video/video_placeholder.dart
new file mode 100644
index 0000000000000..a7d853b1451bf
--- /dev/null
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/video/video_placeholder.dart
@@ -0,0 +1,136 @@
+import 'package:flutter/material.dart';
+
+import 'package:appflowy/generated/locale_keys.g.dart';
+import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
+import 'package:appflowy/plugins/document/application/prelude.dart';
+import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/mobile_block_action_buttons.dart';
+import 'package:appflowy/plugins/document/presentation/editor_plugins/video/upload_video_menu.dart';
+import 'package:appflowy/workspace/presentation/home/toast.dart';
+import 'package:appflowy_editor/appflowy_editor.dart' hide Log, UploadImageMenu;
+import 'package:appflowy_editor_plugins/appflowy_editor_plugins.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:flowy_infra_ui/style_widget/hover.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:go_router/go_router.dart';
+import 'package:string_validator/string_validator.dart';
+
+class VideoPlaceholder extends StatefulWidget {
+ const VideoPlaceholder({super.key, required this.node});
+
+ final Node node;
+
+ @override
+ State createState() => VideoPlaceholderState();
+}
+
+class VideoPlaceholderState extends State {
+ final controller = PopoverController();
+ final documentService = DocumentService();
+ late final editorState = context.read();
+
+ bool showLoading = false;
+
+ @override
+ Widget build(BuildContext context) {
+ final Widget child = DecoratedBox(
+ decoration: BoxDecoration(
+ color: Theme.of(context).colorScheme.surfaceContainerHighest,
+ borderRadius: BorderRadius.circular(4),
+ ),
+ child: FlowyHover(
+ style: HoverStyle(borderRadius: BorderRadius.circular(4)),
+ child: SizedBox(
+ height: 52,
+ child: Row(
+ children: [
+ const HSpace(10),
+ const Icon(Icons.featured_video_outlined, size: 24),
+ const HSpace(10),
+ FlowyText(LocaleKeys.document_plugins_video_emptyLabel.tr()),
+ ],
+ ),
+ ),
+ ),
+ );
+
+ if (PlatformExtension.isDesktopOrWeb) {
+ return AppFlowyPopover(
+ controller: controller,
+ direction: PopoverDirection.bottomWithCenterAligned,
+ constraints: const BoxConstraints(
+ maxWidth: 540,
+ maxHeight: 360,
+ minHeight: 80,
+ ),
+ clickHandler: PopoverClickHandler.gestureDetector,
+ popupBuilder: (_) => UploadVideoMenu(
+ onUrlSubmitted: (url) {
+ controller.close();
+ WidgetsBinding.instance.addPostFrameCallback(
+ (_) async => updateSrc(url),
+ );
+ },
+ ),
+ child: child,
+ );
+ } else {
+ return MobileBlockActionButtons(
+ node: widget.node,
+ editorState: editorState,
+ child: GestureDetector(
+ onTap: () {
+ editorState.updateSelectionWithReason(null, extraInfo: {});
+ showUploadVideoMenu();
+ },
+ child: child,
+ ),
+ );
+ }
+ }
+
+ void showUploadVideoMenu() {
+ if (PlatformExtension.isDesktopOrWeb) {
+ controller.show();
+ } else {
+ showMobileBottomSheet(
+ context,
+ title: LocaleKeys.document_plugins_video_label.tr(),
+ showHeader: true,
+ showCloseButton: true,
+ showDragHandle: true,
+ builder: (context) => Container(
+ margin: const EdgeInsets.only(top: 12.0),
+ constraints: const BoxConstraints(
+ maxHeight: 340,
+ minHeight: 80,
+ ),
+ child: UploadVideoMenu(
+ onUrlSubmitted: (url) async {
+ context.pop();
+ await updateSrc(url);
+ },
+ ),
+ ),
+ );
+ }
+ }
+
+ Future updateSrc(String url) async {
+ if (url.isEmpty || !isURL(url)) {
+ // show error
+ showSnackBarMessage(
+ context,
+ LocaleKeys.document_imageBlock_error_invalidImage.tr(),
+ );
+ return;
+ }
+
+ final transaction = editorState.transaction;
+ transaction.updateNode(widget.node, {
+ VideoBlockKeys.url: url,
+ });
+ await editorState.apply(transaction);
+ }
+}
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart
index 6196388bd196c..0b3aca1ce7fd3 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart
@@ -1,5 +1,3 @@
-import 'dart:math';
-
import 'package:appflowy/core/helpers/url_launcher.dart';
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
@@ -15,6 +13,7 @@ import 'package:appflowy/workspace/application/settings/appearance/appearance_cu
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
import 'package:collection/collection.dart';
+import 'package:flowy_infra/theme_extension.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -42,6 +41,7 @@ class EditorStyleCustomizer {
EditorStyle desktop() {
final theme = Theme.of(context);
+ final afThemeExtension = AFThemeExtension.of(context);
final appearanceFont = context.read().state.font;
final appearance = context.read().state;
final fontSize = appearance.fontSize;
@@ -58,10 +58,12 @@ class EditorStyleCustomizer {
DefaultAppearanceSettings.getDefaultSelectionColor(context),
defaultTextDirection: appearance.defaultTextDirection,
textStyleConfiguration: TextStyleConfiguration(
+ lineHeight: 1.2,
+ applyHeightToFirstAscent: true,
+ applyHeightToLastDescent: true,
text: baseTextStyle(fontFamily).copyWith(
fontSize: fontSize,
- color: theme.colorScheme.onBackground,
- height: 1.5,
+ color: afThemeExtension.onBackground,
),
bold: baseTextStyle(fontFamily, fontWeight: FontWeight.bold).copyWith(
fontWeight: FontWeight.w600,
@@ -79,7 +81,7 @@ class EditorStyleCustomizer {
),
code: GoogleFonts.robotoMono(
textStyle: baseTextStyle(fontFamily).copyWith(
- fontSize: fontSize - 2,
+ fontSize: fontSize,
fontWeight: FontWeight.normal,
color: Colors.red,
backgroundColor: theme.colorScheme.inverseSurface.withOpacity(0.8),
@@ -93,6 +95,7 @@ class EditorStyleCustomizer {
}
EditorStyle mobile() {
+ final afThemeExtension = AFThemeExtension.of(context);
final pageStyle = context.read().state;
final theme = Theme.of(context);
final fontSize = pageStyle.fontLayout.fontSize;
@@ -103,15 +106,14 @@ class EditorStyleCustomizer {
final textScaleFactor =
context.read().state.textScaleFactor;
final baseTextStyle = this.baseTextStyle(fontFamily);
- final codeFontSize = max(0.0, fontSize - 2);
return EditorStyle.mobile(
padding: padding,
defaultTextDirection: defaultTextDirection,
textStyleConfiguration: TextStyleConfiguration(
+ lineHeight: lineHeight,
text: baseTextStyle.copyWith(
fontSize: fontSize,
- color: theme.colorScheme.onBackground,
- height: lineHeight,
+ color: afThemeExtension.onBackground,
),
bold: baseTextStyle.copyWith(fontWeight: FontWeight.w600),
italic: baseTextStyle.copyWith(fontStyle: FontStyle.italic),
@@ -125,7 +127,7 @@ class EditorStyleCustomizer {
),
code: GoogleFonts.robotoMono(
textStyle: baseTextStyle.copyWith(
- fontSize: codeFontSize,
+ fontSize: fontSize,
fontWeight: FontWeight.normal,
fontStyle: FontStyle.italic,
color: Colors.red,
@@ -177,7 +179,15 @@ class EditorStyleCustomizer {
return baseTextStyle(fontFamily).copyWith(
fontSize: fontSize,
height: 1.5,
- color: Theme.of(context).colorScheme.onBackground,
+ color: AFThemeExtension.of(context).onBackground,
+ );
+ }
+
+ TextStyle calloutBlockStyleBuilder() {
+ final fontSize = context.read().state.fontSize;
+ return baseTextStyle(null).copyWith(
+ fontSize: fontSize,
+ height: 1.5,
);
}
@@ -187,16 +197,17 @@ class EditorStyleCustomizer {
fontFamily: defaultFontFamily,
fontSize: fontSize,
height: 1.5,
- color: Theme.of(context).colorScheme.onBackground.withOpacity(0.6),
+ color: AFThemeExtension.of(context).onBackground.withOpacity(0.6),
);
}
SelectionMenuStyle selectionMenuStyleBuilder() {
final theme = Theme.of(context);
+ final afThemeExtension = AFThemeExtension.of(context);
return SelectionMenuStyle(
selectionMenuBackgroundColor: theme.cardColor,
- selectionMenuItemTextColor: theme.colorScheme.onBackground,
- selectionMenuItemIconColor: theme.colorScheme.onBackground,
+ selectionMenuItemTextColor: afThemeExtension.onBackground,
+ selectionMenuItemIconColor: afThemeExtension.onBackground,
selectionMenuItemSelectedIconColor: theme.colorScheme.onSurface,
selectionMenuItemSelectedTextColor: theme.colorScheme.onSurface,
selectionMenuItemSelectedColor: theme.hoverColor,
@@ -205,10 +216,11 @@ class EditorStyleCustomizer {
InlineActionsMenuStyle inlineActionsMenuStyleBuilder() {
final theme = Theme.of(context);
+ final afThemeExtension = AFThemeExtension.of(context);
return InlineActionsMenuStyle(
backgroundColor: theme.cardColor,
- groupTextColor: theme.colorScheme.onBackground.withOpacity(.8),
- menuItemTextColor: theme.colorScheme.onBackground,
+ groupTextColor: afThemeExtension.onBackground.withOpacity(.8),
+ menuItemTextColor: afThemeExtension.onBackground,
menuItemSelectedColor: theme.colorScheme.secondary,
menuItemSelectedTextColor: theme.colorScheme.onSurface,
);
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/share/share_button.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/share/share_button.dart
index fa15bc10d5824..eb82f3d1faec3 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/share/share_button.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/share/share_button.dart
@@ -41,12 +41,9 @@ class DocumentShareButton extends StatelessWidget {
);
},
child: BlocBuilder