From d4f9c71ec233627a90bf8bb64e8ab89e9565a52f Mon Sep 17 00:00:00 2001 From: Richard Shiue <71320345+richardshiue@users.noreply.github.com> Date: Mon, 16 Jun 2025 11:32:19 +0800 Subject: [PATCH] refactor: rename dialog (#8059) * refactor: rename dialog * test: fix test --- .../sidebar/sidebar_rename_untitled_test.dart | 12 +- .../workspace/change_name_and_icon_test.dart | 2 +- .../collaborative_workspace_test.dart | 17 +- .../document_create_and_delete_test.dart | 2 +- .../document/document_sub_page_test.dart | 5 +- .../shared/common_operations.dart | 27 ++- .../shared/database_test_op.dart | 14 +- .../integration_test/shared/workspace.dart | 33 ++-- .../presentation/shared_section.dart | 11 +- .../tab_bar/desktop/tab_bar_header.dart | 10 +- .../favorites/favorite_more_actions.dart | 12 +- .../sidebar/space/sidebar_space_header.dart | 11 +- .../workspace/_sidebar_workspace_actions.dart | 11 +- .../workspace/_sidebar_workspace_menu.dart | 28 +-- .../home/menu/view/view_item.dart | 13 +- .../presentation/widgets/dialog_v2.dart | 160 +++++++++++++++++- .../presentation/widgets/dialogs.dart | 103 ----------- .../lib/src/component/modal/modal.dart | 17 +- .../src/component/textfield/textfield.dart | 7 + 19 files changed, 269 insertions(+), 226 deletions(-) diff --git a/frontend/appflowy_flutter/integration_test/desktop/cloud/sidebar/sidebar_rename_untitled_test.dart b/frontend/appflowy_flutter/integration_test/desktop/cloud/sidebar/sidebar_rename_untitled_test.dart index 8226b68b26..09ac36b8ce 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/cloud/sidebar/sidebar_rename_untitled_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/cloud/sidebar/sidebar_rename_untitled_test.dart @@ -1,10 +1,10 @@ import 'package:appflowy/env/cloud_env.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; -import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialog_v2.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pbenum.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra_ui/style_widget/text_input.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -38,12 +38,12 @@ void main() { ); await tester.pumpAndSettle(); - expect(find.byType(NavigatorTextFieldDialog), findsOneWidget); + expect(find.byType(AFTextFieldDialog), findsOneWidget); - final textField = tester.widget( + final textField = tester.widget( find.descendant( - of: find.byType(NavigatorTextFieldDialog), - matching: find.byType(FlowyFormTextInput), + of: find.byType(AFTextFieldDialog), + matching: find.byType(AFTextField), ), ); diff --git a/frontend/appflowy_flutter/integration_test/desktop/cloud/workspace/change_name_and_icon_test.dart b/frontend/appflowy_flutter/integration_test/desktop/cloud/workspace/change_name_and_icon_test.dart index fd6e4055d0..6a4a3e33d3 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/cloud/workspace/change_name_and_icon_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/cloud/workspace/change_name_and_icon_test.dart @@ -48,7 +48,7 @@ void main() { find.byType(WorkspaceIcon), ); expect(workspaceIcon.workspaceIcon, icon); - expect(find.findTextInFlowyText(name), findsOneWidget); + expect(workspaceIcon.workspaceName, name); }); testWidgets('verify the result again after relaunching', (tester) async { diff --git a/frontend/appflowy_flutter/integration_test/desktop/cloud/workspace/collaborative_workspace_test.dart b/frontend/appflowy_flutter/integration_test/desktop/cloud/workspace/collaborative_workspace_test.dart index 999ae43bb6..914c9d27d7 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/cloud/workspace/collaborative_workspace_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/cloud/workspace/collaborative_workspace_test.dart @@ -36,23 +36,20 @@ void main() { final loading = find.byType(Loading); await tester.pumpUntilNotFound(loading); - Finder success; - - final Finder items = find.byType(WorkspaceMenuItem); - // delete the newly created workspace await tester.openCollaborativeWorkspaceMenu(); - await tester.pumpUntilFound(items); + final items = find.byType(WorkspaceMenuItem); expect(items, findsNWidgets(2)); + + final lastWorkspace = items.last; expect( - tester.widget(items.last).workspace.name, + tester.widget(lastWorkspace).workspace.name, name, ); - final secondWorkspace = find.byType(WorkspaceMenuItem).last; await tester.hoverOnWidget( - secondWorkspace, + lastWorkspace, onHover: () async { // click the more button final moreButton = find.byType(WorkspaceMoreActionList); @@ -68,10 +65,8 @@ void main() { expect(confirm, findsOneWidget); await tester.tapButton(find.text(LocaleKeys.button_ok.tr())); // delete success - success = find.text(LocaleKeys.workspace_createSuccess.tr()); + final success = find.text(LocaleKeys.workspace_createSuccess.tr()); await tester.pumpUntilFound(success); - expect(success, findsOneWidget); - await tester.pumpUntilNotFound(success); }, ); }); diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_create_and_delete_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_create_and_delete_test.dart index c2e00a4b48..d88c540f2b 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/document/document_create_and_delete_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_create_and_delete_test.dart @@ -7,7 +7,7 @@ import '../../shared/util.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - group('create and delete the document', () { + group('create and delete the document:', () { testWidgets('create a new document when launching app in first time', (tester) async { await tester.initializeAppFlowy(); diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_sub_page_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_sub_page_test.dart index 50f0f903bc..b54b45c7aa 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/document/document_sub_page_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_sub_page_test.dart @@ -7,6 +7,7 @@ import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart'; import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -521,8 +522,8 @@ extension _SubPageTestHelper on WidgetTester { await hoverOnPageName(currentName, onHover: () async => pumpAndSettle()); await rightClickOnPageName(currentName); await tapButtonWithName(ViewMoreActionType.rename.name); - await enterText(find.byType(TextFormField), newName); - await tapOKButton(); + await enterText(find.byType(AFTextField), newName); + await tapButton(find.text(LocaleKeys.button_confirm.tr())); await pumpAndSettle(); } } diff --git a/frontend/appflowy_flutter/integration_test/shared/common_operations.dart b/frontend/appflowy_flutter/integration_test/shared/common_operations.dart index e44d4bad37..3cd6a6ebd9 100644 --- a/frontend/appflowy_flutter/integration_test/shared/common_operations.dart +++ b/frontend/appflowy_flutter/integration_test/shared/common_operations.dart @@ -38,11 +38,13 @@ import 'package:appflowy/workspace/presentation/notifications/widgets/notificati import 'package:appflowy/workspace/presentation/notifications/widgets/notification_tab_bar.dart'; import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialog_v2.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'; +import 'package:appflowy_ui/appflowy_ui.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/widget/buttons/primary_button.dart'; import 'package:flutter/foundation.dart'; @@ -264,8 +266,8 @@ extension CommonOperations on WidgetTester { /// Rename the page. Future renamePage(String name) async { await tapRenamePageButton(); - await enterText(find.byType(TextFormField), name); - await tapOKButton(); + await enterText(find.byType(AFTextField), name); + await tapButton(find.text(LocaleKeys.button_confirm.tr())); } Future tapTrashButton() async { @@ -359,7 +361,7 @@ extension CommonOperations on WidgetTester { ); final showRenameDialog = settingsOrFailure ?? false; if (showRenameDialog) { - await tapOKButton(); + await tapButton(find.text(LocaleKeys.button_confirm.tr())); } await pumpAndSettle(); @@ -736,8 +738,7 @@ extension CommonOperations on WidgetTester { final workspace = find.byType(SidebarWorkspace); expect(workspace, findsOneWidget); - await tapButton(workspace, pumpAndSettle: false); - await pump(const Duration(seconds: 5)); + await tapButton(workspace, milliseconds: 5000); } Future createCollaborativeWorkspace(String name) async { @@ -752,22 +753,20 @@ extension CommonOperations on WidgetTester { // click the create button final createButton = find.byKey(createWorkspaceButtonKey); expect(createButton, findsOneWidget); - await tapButton(createButton, pumpAndSettle: false); - await pump(const Duration(seconds: 5)); - - // see the create workspace dialog - final createWorkspaceDialog = find.byType(CreateWorkspaceDialog); - expect(createWorkspaceDialog, findsOneWidget); + await tapButton(createButton); // input the workspace name final workspaceNameInput = find.descendant( - of: createWorkspaceDialog, + of: find.byType(AFTextFieldDialog), matching: find.byType(TextField), ); await enterText(workspaceNameInput, name); + await pumpAndSettle(); - await tapButtonWithName(LocaleKeys.button_ok.tr(), pumpAndSettle: false); - await pump(const Duration(seconds: 5)); + await tapButton( + find.text(LocaleKeys.button_confirm.tr()), + milliseconds: 2000, + ); } // For mobile platform to launch the app in anonymous mode diff --git a/frontend/appflowy_flutter/integration_test/shared/database_test_op.dart b/frontend/appflowy_flutter/integration_test/shared/database_test_op.dart index 507260156e..4005e92efe 100644 --- a/frontend/appflowy_flutter/integration_test/shared/database_test_op.dart +++ b/frontend/appflowy_flutter/integration_test/shared/database_test_op.dart @@ -76,15 +76,16 @@ import 'package:appflowy/util/field_type_extension.dart'; import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/clear_date_button.dart'; import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/date_type_option_button.dart'; import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/reminder_selector.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialog_v2.dart'; import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_board/appflowy_board.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; import 'package:calendar_view/calendar_view.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/style_widget/text_input.dart'; import 'package:flowy_infra_ui/widget/buttons/primary_button.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; @@ -1646,18 +1647,13 @@ extension AppFlowyDatabaseTest on WidgetTester { await enterText( find.descendant( - of: find.byType(FlowyFormTextInput), - matching: find.byType(TextFormField), + of: find.byType(AFTextFieldDialog), + matching: find.byType(AFTextField), ), name, ); - final field = find.byWidgetPredicate( - (widget) => - widget is PrimaryTextButton && - widget.label == LocaleKeys.button_ok.tr(), - ); - await tapButton(field); + await tapButton(find.text(LocaleKeys.button_confirm.tr())); } Future deleteDatebaseView(Finder linkedView) async { diff --git a/frontend/appflowy_flutter/integration_test/shared/workspace.dart b/frontend/appflowy_flutter/integration_test/shared/workspace.dart index 1b2f22b944..5fd4269a31 100644 --- a/frontend/appflowy_flutter/integration_test/shared/workspace.dart +++ b/frontend/appflowy_flutter/integration_test/shared/workspace.dart @@ -4,8 +4,9 @@ import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sid 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:appflowy/workspace/presentation/widgets/dialog_v2.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'util.dart'; @@ -31,26 +32,24 @@ extension AppFlowyWorkspace on WidgetTester { } Future changeWorkspaceName(String name) async { - final moreButton = find.descendant( - of: find.byType(WorkspaceMenuItem), - matching: find.byType(WorkspaceMoreActionList), - ); - expect(moreButton, findsOneWidget); + final menuItem = find.byType(WorkspaceMenuItem); + expect(menuItem, findsOneWidget); await hoverOnWidget( - moreButton, + menuItem, onHover: () async { - await tapButton(moreButton); - // wait for the menu to open - final renameButton = find.findTextInFlowyText( - LocaleKeys.button_rename.tr(), + await tapButton( + find.descendant( + of: menuItem, + matching: find.byType(WorkspaceMoreActionList), + ), + ); + await tapButton(find.text(LocaleKeys.button_rename.tr())); + final input = find.descendant( + of: find.byType(AFTextFieldDialog), + matching: find.byType(AFTextField), ); - await pumpUntilFound(renameButton); - expect(renameButton, findsOneWidget); - await tapButton(renameButton); - final input = find.byType(TextFormField); - expect(input, findsOneWidget); await enterText(input, name); - await tapButton(find.text(LocaleKeys.button_ok.tr())); + await tapButton(find.text(LocaleKeys.button_confirm.tr())); }, ); } diff --git a/frontend/appflowy_flutter/lib/features/shared_section/presentation/shared_section.dart b/frontend/appflowy_flutter/lib/features/shared_section/presentation/shared_section.dart index d4a7384038..56d419ac81 100644 --- a/frontend/appflowy_flutter/lib/features/shared_section/presentation/shared_section.dart +++ b/frontend/appflowy_flutter/lib/features/shared_section/presentation/shared_section.dart @@ -13,6 +13,7 @@ import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy/workspace/application/view/view_service.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart'; import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialog_v2.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy_ui/appflowy_ui.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -88,19 +89,19 @@ class SharedSection extends StatelessWidget { context.read().openTab(view); break; case ViewMoreActionType.rename: - await NavigatorTextFieldDialog( + await showAFTextFieldDialog( + context: context, title: LocaleKeys.disclosureAction_rename.tr(), - autoSelectAllText: true, - value: view.nameOrDefault, + initialValue: view.nameOrDefault, maxLength: 256, - onConfirm: (newValue, _) { + onConfirm: (newValue) { // can not use bloc here because it has been disposed. ViewBackendService.updateView( viewId: view.id, name: newValue, ); }, - ).show(context); + ); break; case ViewMoreActionType.leaveSharedPage: // show a dialog to confirm the action diff --git a/frontend/appflowy_flutter/lib/plugins/database/tab_bar/desktop/tab_bar_header.dart b/frontend/appflowy_flutter/lib/plugins/database/tab_bar/desktop/tab_bar_header.dart index 32bab95a4e..08c5c7b18b 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/tab_bar/desktop/tab_bar_header.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/tab_bar/desktop/tab_bar_header.dart @@ -7,6 +7,7 @@ import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy/workspace/application/view/view_service.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialog_v2.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; @@ -227,10 +228,11 @@ class _TabBarItemButtonState extends State { action: TabBarViewAction.rename, itemHeight: ActionListSizes.itemHeight, onSelected: (action) { - NavigatorTextFieldDialog( + showAFTextFieldDialog( + context: context, title: LocaleKeys.menuAppHeader_renameDialog.tr(), - value: widget.view.nameOrDefault, - onConfirm: (newValue, _) { + initialValue: widget.view.nameOrDefault, + onConfirm: (newValue) { context.read().add( DatabaseTabBarEvent.renameView( widget.view.id, @@ -238,7 +240,7 @@ class _TabBarItemButtonState extends State { ), ); }, - ).show(context); + ); menuController.close(); }, ), diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/favorites/favorite_more_actions.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/favorites/favorite_more_actions.dart index 09b8a44842..781ca71b6a 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/favorites/favorite_more_actions.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/favorites/favorite_more_actions.dart @@ -9,7 +9,7 @@ import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy/workspace/application/view/view_service.dart'; import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart'; import 'package:appflowy/workspace/presentation/home/menu/view/view_more_action_button.dart'; -import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialog_v2.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -41,19 +41,19 @@ class FavoriteMoreActions extends StatelessWidget { PopoverContainer.maybeOf(context)?.closeAll(); break; case ViewMoreActionType.rename: - NavigatorTextFieldDialog( + showAFTextFieldDialog( + context: context, title: LocaleKeys.disclosureAction_rename.tr(), - autoSelectAllText: true, - value: view.nameOrDefault, + initialValue: view.nameOrDefault, maxLength: 256, - onConfirm: (newValue, _) { + onConfirm: (newValue) { // can not use bloc here because it has been disposed. ViewBackendService.updateView( viewId: view.id, name: newValue, ); }, - ).show(context); + ); PopoverContainer.maybeOf(context)?.closeAll(); break; diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/sidebar_space_header.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/sidebar_space_header.dart index cf4a2aa5b1..550dd11b9c 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/sidebar_space_header.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/sidebar_space_header.dart @@ -11,6 +11,7 @@ import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_w import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_action_type.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_more_popup.dart'; import 'package:appflowy/workspace/presentation/home/menu/view/view_add_button.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialog_v2.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; @@ -213,12 +214,12 @@ class _SidebarSpaceHeaderState extends State { } Future _showRenameDialog() async { - await NavigatorTextFieldDialog( + await showAFTextFieldDialog( + context: context, title: LocaleKeys.space_rename.tr(), - value: widget.space.name, - autoSelectAllText: true, + initialValue: widget.space.name, hintText: LocaleKeys.space_spaceName.tr(), - onConfirm: (name, _) { + onConfirm: (name) { context.read().add( SpaceEvent.rename( space: widget.space, @@ -226,7 +227,7 @@ class _SidebarSpaceHeaderState extends State { ), ); }, - ).show(context); + ); } void _showManageSpaceDialog(BuildContext context) { diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart index cdf7884054..8582270858 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart @@ -3,6 +3,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/shared/af_role_pb_extension.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/members/workspace_member_bloc.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialog_v2.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; @@ -155,12 +156,12 @@ class _WorkspaceMoreActionWrapper extends CustomActionCell { }, ); case WorkspaceMoreAction.rename: - await NavigatorTextFieldDialog( + await showAFTextFieldDialog( + context: context, title: LocaleKeys.workspace_renameWorkspace.tr(), - value: workspace.name, + initialValue: workspace.name, hintText: '', - autoSelectAllText: true, - onConfirm: (name, context) async { + onConfirm: (name) async { workspaceBloc.add( UserWorkspaceEvent.renameWorkspace( workspaceId: workspace.workspaceId, @@ -168,7 +169,7 @@ class _WorkspaceMoreActionWrapper extends CustomActionCell { ), ); }, - ).show(context); + ); case WorkspaceMoreAction.leave: await showConfirmDialog( context: context, diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart index f4fa23138c..a3dec48b49 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart @@ -9,6 +9,7 @@ import 'package:appflowy/workspace/application/tabs/tabs_bloc.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/settings/widgets/members/workspace_member_bloc.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialog_v2.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; @@ -333,26 +334,6 @@ class _WorkspaceInfo extends StatelessWidget { } } -class CreateWorkspaceDialog extends StatelessWidget { - const CreateWorkspaceDialog({ - super.key, - required this.onConfirm, - }); - - final void Function(String name) onConfirm; - - @override - Widget build(BuildContext context) { - return NavigatorTextFieldDialog( - title: LocaleKeys.workspace_create.tr(), - value: '', - hintText: '', - autoSelectAllText: true, - onConfirm: (name, _) => onConfirm(name), - ); - } -} - class _CreateWorkspaceButton extends StatelessWidget { const _CreateWorkspaceButton(); @@ -399,7 +380,10 @@ class _CreateWorkspaceButton extends StatelessWidget { Future _showCreateWorkspaceDialog(BuildContext context) async { if (context.mounted) { final workspaceBloc = context.read(); - await CreateWorkspaceDialog( + await showAFTextFieldDialog( + context: context, + title: LocaleKeys.workspace_create.tr(), + initialValue: '', onConfirm: (name) { workspaceBloc.add( UserWorkspaceEvent.createWorkspace( @@ -408,7 +392,7 @@ class _CreateWorkspaceButton extends StatelessWidget { ), ); }, - ).show(context); + ); } } } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_item.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_item.dart index f6324b42a8..ff4c78797d 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_item.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_item.dart @@ -21,6 +21,7 @@ import 'package:appflowy/workspace/presentation/home/menu/view/draggable_view_it import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart'; import 'package:appflowy/workspace/presentation/home/menu/view/view_add_button.dart'; import 'package:appflowy/workspace/presentation/home/menu/view/view_more_action_button.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialog_v2.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy/workspace/presentation/widgets/more_view_actions/widgets/lock_page_action.dart'; import 'package:appflowy/workspace/presentation/widgets/rename_view_popover.dart'; @@ -766,15 +767,15 @@ class _SingleInnerViewItemState extends State { break; case ViewMoreActionType.rename: unawaited( - NavigatorTextFieldDialog( + showAFTextFieldDialog( + context: context, title: LocaleKeys.disclosureAction_rename.tr(), - autoSelectAllText: true, - value: widget.view.nameOrDefault, - maxLength: 256, - onConfirm: (newValue, _) { + initialValue: widget.view.nameOrDefault, + onConfirm: (newValue) { context.read().add(ViewEvent.rename(newValue)); }, - ).show(context), + maxLength: 256, + ), ); break; case ViewMoreActionType.delete: diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialog_v2.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialog_v2.dart index 866274a57e..9bfaf3f753 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialog_v2.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialog_v2.dart @@ -1,5 +1,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; typedef SimpleAFDialogAction = (String, void Function(BuildContext)?); @@ -36,9 +38,6 @@ Future showSimpleAFDialog({ AFModalHeader( leading: Text( title, - style: theme.textStyle.heading4.prominent( - color: theme.textColorScheme.primary, - ), ), trailing: [ AFGhostButton.normal( @@ -100,3 +99,158 @@ Future showSimpleAFDialog({ }, ); } + +/// Shows a dialog for renaming an item with a text field. +/// The API is flexible: either provide a callback for confirmation or use the +/// returned Future to get the new value. +/// +Future showAFTextFieldDialog({ + required BuildContext context, + required String title, + required String initialValue, + void Function(String)? onConfirm, + bool barrierDismissible = true, + bool selectAll = true, + int? maxLength, + String? hintText, +}) { + return showDialog( + context: context, + barrierColor: AppFlowyTheme.of(context).surfaceColorScheme.overlay, + barrierDismissible: barrierDismissible, + builder: (context) { + return AFTextFieldDialog( + title: title, + initialValue: initialValue, + onConfirm: onConfirm, + selectAll: selectAll, + maxLength: maxLength, + hintText: hintText, + ); + }, + ); +} + +class AFTextFieldDialog extends StatefulWidget { + const AFTextFieldDialog({ + super.key, + required this.title, + required this.initialValue, + this.onConfirm, + this.selectAll = true, + this.maxLength, + this.hintText, + }); + + final String title; + final String initialValue; + final void Function(String)? onConfirm; + final bool selectAll; + final int? maxLength; + final String? hintText; + + @override + State createState() => _AFTextFieldDialogState(); +} + +class _AFTextFieldDialogState extends State { + final textController = TextEditingController(); + + @override + void initState() { + super.initState(); + textController.value = TextEditingValue( + text: widget.initialValue, + selection: widget.selectAll + ? TextSelection( + baseOffset: 0, + extentOffset: widget.initialValue.length, + ) + : TextSelection.collapsed( + offset: widget.initialValue.length, + ), + ); + } + + @override + void dispose() { + textController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); + + return AFModal( + constraints: BoxConstraints( + maxWidth: AFModalDimension.S, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + AFModalHeader( + leading: Text( + widget.title, + ), + trailing: [ + AFGhostButton.normal( + onTap: () => Navigator.of(context).pop(), + padding: EdgeInsets.all(theme.spacing.xs), + builder: (context, isHovering, disabled) { + return FlowySvg( + FlowySvgs.toast_close_s, + size: Size.square(20), + ); + }, + ), + ], + ), + Flexible( + child: AFModalBody( + child: AFTextField( + autoFocus: true, + size: AFTextFieldSize.m, + hintText: widget.hintText, + maxLength: widget.maxLength, + controller: textController, + onSubmitted: (_) { + handleConfirm(); + }, + ), + ), + ), + AFModalFooter( + trailing: [ + AFOutlinedTextButton.normal( + text: LocaleKeys.button_cancel.tr(), + onTap: () => Navigator.of(context).pop(), + ), + ValueListenableBuilder( + valueListenable: textController, + builder: (contex, value, child) { + return AFFilledTextButton.primary( + text: LocaleKeys.button_confirm.tr(), + disabled: value.text.trim().isEmpty, + onTap: handleConfirm, + ); + }, + ), + ], + ), + ], + ), + ); + } + + void handleConfirm() { + final text = textController.text.trim(); + + if (text.isEmpty) { + return; + } + + widget.onConfirm?.call(text); + Navigator.of(context).pop(text); + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialogs.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialogs.dart index df98bda771..22f375148f 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialogs.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialogs.dart @@ -1,12 +1,10 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/startup/tasks/app_widget.dart'; import 'package:appflowy/util/theme_extension.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/size.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; -import 'package:flowy_infra_ui/style_widget/text_input.dart'; import 'package:flowy_infra_ui/widget/buttons/primary_button.dart'; import 'package:flowy_infra_ui/widget/buttons/secondary_button.dart'; import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart'; @@ -74,107 +72,6 @@ class _NavigatorCustomDialog extends State { } } -class NavigatorTextFieldDialog extends StatefulWidget { - const NavigatorTextFieldDialog({ - super.key, - required this.title, - this.autoSelectAllText = false, - required this.value, - required this.onConfirm, - this.onCancel, - this.maxLength, - this.hintText, - }); - - final String value; - final String title; - final VoidCallback? onCancel; - final void Function(String, BuildContext) onConfirm; - final bool autoSelectAllText; - final int? maxLength; - final String? hintText; - - @override - State createState() => - _NavigatorTextFieldDialogState(); -} - -class _NavigatorTextFieldDialogState extends State { - String newValue = ""; - final controller = TextEditingController(); - - @override - void initState() { - super.initState(); - newValue = widget.value; - controller.text = newValue; - if (widget.autoSelectAllText) { - controller.selection = TextSelection( - baseOffset: 0, - extentOffset: newValue.length, - ); - } - } - - @override - void dispose() { - controller.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return StyledDialog( - child: Column( - children: [ - FlowyText.medium( - widget.title, - color: Theme.of(context).colorScheme.tertiary, - fontSize: FontSizes.s16, - ), - VSpace(Insets.m), - FlowyFormTextInput( - hintText: - widget.hintText ?? LocaleKeys.dialogCreatePageNameHint.tr(), - controller: controller, - textStyle: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith(fontSize: FontSizes.s16), - maxLength: widget.maxLength, - showCounter: false, - autoFocus: true, - onChanged: (text) { - newValue = text; - }, - onEditingComplete: () { - widget.onConfirm(newValue, context); - AppGlobals.nav.pop(); - }, - ), - VSpace(Insets.xl), - OkCancelButton( - onOkPressed: () { - if (newValue.isEmpty) { - showToastNotification( - message: LocaleKeys.space_spaceNameCannotBeEmpty.tr(), - ); - return; - } - widget.onConfirm(newValue, context); - Navigator.of(context).pop(); - }, - onCancelPressed: () { - widget.onCancel?.call(); - Navigator.of(context).pop(); - }, - ), - ], - ), - ); - } -} - class NavigatorAlertDialog extends StatefulWidget { const NavigatorAlertDialog({ super.key, diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/modal/modal.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/modal/modal.dart index 3b37fe2e64..08d4b97ed4 100644 --- a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/modal/modal.dart +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/modal/modal.dart @@ -61,12 +61,17 @@ class AFModalHeader extends StatelessWidget { left: theme.spacing.xxl, right: theme.spacing.xxl, ), - child: Row( - spacing: theme.spacing.s, - children: [ - Expanded(child: leading), - ...trailing, - ], + child: DefaultTextStyle( + style: theme.textStyle.heading4.prominent( + color: theme.textColorScheme.primary, + ), + child: Row( + spacing: theme.spacing.s, + children: [ + Expanded(child: leading), + ...trailing, + ], + ), ), ); } diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/textfield/textfield.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/textfield/textfield.dart index 3f8462e932..e3d38ba97e 100644 --- a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/textfield/textfield.dart +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/textfield/textfield.dart @@ -1,5 +1,6 @@ import 'package:appflowy_ui/src/theme/theme.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; typedef AFTextFieldValidator = (bool result, String errorText) Function( TextEditingController controller, @@ -32,6 +33,7 @@ class AFTextField extends StatefulWidget { this.groupId = EditableText, this.focusNode, this.readOnly = false, + this.maxLength, }); /// The hint text to display when the text field is empty. @@ -82,6 +84,9 @@ class AFTextField extends StatefulWidget { /// Readonly. final bool readOnly; + /// The maximum length of the text field. + final int? maxLength; + @override State createState() => _AFTextFieldState(); } @@ -181,6 +186,8 @@ class _AFTextFieldState extends AFTextFieldState { onChanged: widget.onChanged, onSubmitted: widget.onSubmitted, autofocus: widget.autoFocus ?? false, + maxLength: widget.maxLength, + maxLengthEnforcement: MaxLengthEnforcement.truncateAfterCompositionEnds, decoration: InputDecoration( hintText: widget.hintText, hintStyle: theme.textStyle.body.standard(