refactor: rename dialog (#8059)

* refactor: rename dialog

* test: fix test
This commit is contained in:
Richard Shiue 2025-06-16 11:32:19 +08:00 committed by GitHub
parent a480889c28
commit d4f9c71ec2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 269 additions and 226 deletions

View File

@ -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<FlowyFormTextInput>(
final textField = tester.widget<AFTextField>(
find.descendant(
of: find.byType(NavigatorTextFieldDialog),
matching: find.byType(FlowyFormTextInput),
of: find.byType(AFTextFieldDialog),
matching: find.byType(AFTextField),
),
);

View File

@ -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 {

View File

@ -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<WorkspaceMenuItem>(items.last).workspace.name,
tester.widget<WorkspaceMenuItem>(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);
},
);
});

View File

@ -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();

View File

@ -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();
}
}

View File

@ -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<void> 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<void> 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<void> 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

View File

@ -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<void> deleteDatebaseView(Finder linkedView) async {

View File

@ -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<void> 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()));
},
);
}

View File

@ -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<TabsBloc>().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

View File

@ -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<TabBarItemButton> {
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<DatabaseTabBarBloc>().add(
DatabaseTabBarEvent.renameView(
widget.view.id,
@ -238,7 +240,7 @@ class _TabBarItemButtonState extends State<TabBarItemButton> {
),
);
},
).show(context);
);
menuController.close();
},
),

View File

@ -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;

View File

@ -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<SidebarSpaceHeader> {
}
Future<void> _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<SpaceBloc>().add(
SpaceEvent.rename(
space: widget.space,
@ -226,7 +227,7 @@ class _SidebarSpaceHeaderState extends State<SidebarSpaceHeader> {
),
);
},
).show(context);
);
}
void _showManageSpaceDialog(BuildContext context) {

View File

@ -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,

View File

@ -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<void> _showCreateWorkspaceDialog(BuildContext context) async {
if (context.mounted) {
final workspaceBloc = context.read<UserWorkspaceBloc>();
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);
);
}
}
}

View File

@ -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<SingleInnerViewItem> {
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<ViewBloc>().add(ViewEvent.rename(newValue));
},
).show(context),
maxLength: 256,
),
);
break;
case ViewMoreActionType.delete:

View File

@ -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<void> showSimpleAFDialog({
AFModalHeader(
leading: Text(
title,
style: theme.textStyle.heading4.prominent(
color: theme.textColorScheme.primary,
),
),
trailing: [
AFGhostButton.normal(
@ -100,3 +99,158 @@ Future<void> 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<String?> 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<String?>(
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<AFTextFieldDialog> createState() => _AFTextFieldDialogState();
}
class _AFTextFieldDialogState extends State<AFTextFieldDialog> {
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);
}
}

View File

@ -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<NavigatorCustomDialog> {
}
}
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<NavigatorTextFieldDialog> createState() =>
_NavigatorTextFieldDialogState();
}
class _NavigatorTextFieldDialogState extends State<NavigatorTextFieldDialog> {
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: <Widget>[
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,

View File

@ -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,
],
),
),
);
}

View File

@ -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<AFTextField> 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(