diff --git a/frontend/appflowy_flutter/integration_test/database_cell_test.dart b/frontend/appflowy_flutter/integration_test/database_cell_test.dart index f2a0f15c8d..a01ea6609f 100644 --- a/frontend/appflowy_flutter/integration_test/database_cell_test.dart +++ b/frontend/appflowy_flutter/integration_test/database_cell_test.dart @@ -181,5 +181,30 @@ void main() { await tester.pumpAndSettle(); }); + + testWidgets('edit single select cell', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapGoButton(); + + const fieldType = FieldType.SingleSelect; + await tester.tapAddButton(); + // When create a grid, it will create a single select field by default + await tester.tapCreateGridButton(); + + // Tap the cell to invoke the selection option editor + await tester.tapSelectOptionCellInGrid(rowIndex: 0, fieldType: fieldType); + await tester.findSelectOptionEditor(findsOneWidget); + + await tester.createOption(name: 'hello world'); + await tester.dismissSelectOptionEditor(); + + // Make sure the option is created and displayed in the cell + await tester.findSelectOptionWithNameInGrid( + rowIndex: 0, + name: 'hello world', + ); + + await tester.pumpAndSettle(); + }); }); } diff --git a/frontend/appflowy_flutter/integration_test/database_row_page_test.dart b/frontend/appflowy_flutter/integration_test/database_row_page_test.dart index c9bf59cbbd..10090121b8 100644 --- a/frontend/appflowy_flutter/integration_test/database_row_page_test.dart +++ b/frontend/appflowy_flutter/integration_test/database_row_page_test.dart @@ -71,7 +71,7 @@ void main() { // The emoji already displayed in the row banner final emojiText = find.byWidgetPredicate( - (widget) => widget is FlowyText && widget.title == '😅', + (widget) => widget is FlowyText && widget.text == '😅', ); // The number of emoji should be two. One in the row displayed in the grid @@ -97,7 +97,7 @@ void main() { // Remove the emoji await tester.tapButton(find.byType(RemoveEmojiButton)); final emojiText = find.byWidgetPredicate( - (widget) => widget is FlowyText && widget.title == '😀', + (widget) => widget is FlowyText && widget.text == '😀', ); expect(emojiText, findsNothing); }); diff --git a/frontend/appflowy_flutter/integration_test/util/base.dart b/frontend/appflowy_flutter/integration_test/util/base.dart index d09a96537b..ad9d79d08f 100644 --- a/frontend/appflowy_flutter/integration_test/util/base.dart +++ b/frontend/appflowy_flutter/integration_test/util/base.dart @@ -104,7 +104,7 @@ extension AppFlowyTestBase on WidgetTester { ); if (button.evaluate().isEmpty) { button = find.byWidgetPredicate( - (widget) => widget is FlowyText && widget.title == tr, + (widget) => widget is FlowyText && widget.text == tr, ); } await tapButton( @@ -135,7 +135,7 @@ extension AppFlowyTestBase on WidgetTester { extension AppFlowyFinderTestBase on CommonFinders { Finder findTextInFlowyText(String text) { return find.byWidgetPredicate( - (widget) => widget is FlowyText && widget.title == text, + (widget) => widget is FlowyText && widget.text == text, ); } } diff --git a/frontend/appflowy_flutter/integration_test/util/database_test_op.dart b/frontend/appflowy_flutter/integration_test/util/database_test_op.dart index 415a91cabf..2767ee1ad2 100644 --- a/frontend/appflowy_flutter/integration_test/util/database_test_op.dart +++ b/frontend/appflowy_flutter/integration_test/util/database_test_op.dart @@ -14,12 +14,15 @@ import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/ import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/disclosure_button.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/filter_menu_item.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_cell_action_sheet.dart'; +import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_list.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_option_editor.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/filter_button.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/grid_layout.dart'; -import 'package:appflowy/plugins/database_view/widgets/row/cells/checklist_cell/checklist_progress_bar.dart'; import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart'; +import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/select_option_editor.dart'; +import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/text_field.dart'; +import 'package:appflowy/plugins/database_view/widgets/row/cells/checklist_cell/checklist_progress_bar.dart'; import 'package:appflowy/plugins/database_view/widgets/row/row_document.dart'; import 'package:appflowy/plugins/database_view/widgets/row/cells/date_cell/date_editor.dart'; import 'package:appflowy/plugins/database_view/widgets/setting/database_setting.dart'; @@ -37,7 +40,6 @@ import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart' import 'package:appflowy/plugins/database_view/grid/presentation/widgets/footer/grid_footer.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_cell.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.dart'; -import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/row/row.dart'; import 'package:appflowy/plugins/database_view/widgets/row/accessory/cell_accessory.dart'; import 'package:appflowy/plugins/database_view/widgets/row/cells/cells.dart'; @@ -255,7 +257,7 @@ extension AppFlowyDatabaseTest on WidgetTester { matching: find.byWidgetPredicate( (widget) { if (widget is FlowyText) { - return widget.title == content; + return widget.text == content; } return false; }, @@ -279,6 +281,59 @@ extension AppFlowyDatabaseTest on WidgetTester { await tapButton(finder); } + Future tapSelectOptionCellInGrid({ + required int rowIndex, + required FieldType fieldType, + }) async { + assert( + fieldType == FieldType.SingleSelect || fieldType == FieldType.MultiSelect, + ); + + final findRow = find.byType(GridRow); + final findCell = finderForFieldType(fieldType); + + final cell = find.descendant( + of: findRow.at(rowIndex), + matching: findCell, + ); + + await tapButton(cell); + } + + /// The [SelectOptionCellEditor] must be opened first. + Future createOption({ + required String name, + }) async { + final findEditor = find.byType(SelectOptionCellEditor); + expect(findEditor, findsOneWidget); + + final findTextField = find.byType(SelectOptionTextField); + expect(findTextField, findsOneWidget); + + await enterText(findTextField, name); + await pump(); + + await testTextInput.receiveAction(TextInputAction.done); + await pumpAndSettle(); + } + + Future findSelectOptionWithNameInGrid({ + required int rowIndex, + required String name, + }) async { + final findRow = find.byType(GridRow); + final option = find.byWidgetPredicate( + (widget) => widget is SelectOptionTag && widget.name == name, + ); + + final cell = find.descendant( + of: findRow.at(rowIndex), + matching: option, + ); + + expect(cell, findsOneWidget); + } + Future openFirstRowDetailPage() async { await hoverOnFirstRowOfGrid(); @@ -410,11 +465,10 @@ extension AppFlowyDatabaseTest on WidgetTester { /// Must call [tapTypeOptionButton] first. Future selectFieldType(FieldType fieldType) async { final fieldTypeCell = find.byType(FieldTypeCell); - final fieldTypeButton = find.descendant( of: fieldTypeCell, matching: find.byWidgetPredicate( - (widget) => widget is FlowyText && widget.title == fieldType.title(), + (widget) => widget is FlowyText && widget.text == fieldType.title(), ), ); await tapButton(fieldTypeButton); @@ -493,6 +547,16 @@ extension AppFlowyDatabaseTest on WidgetTester { expect(finder, matcher); } + Future findSelectOptionEditor(dynamic matcher) async { + final finder = find.byType(SelectOptionCellEditor); + expect(finder, matcher); + } + + Future dismissSelectOptionEditor() async { + await sendKeyEvent(LogicalKeyboardKey.escape); + await pumpAndSettle(); + } + Future tapCreateRowButtonInGrid() async { await tapButton(find.byType(GridAddRowButton)); } @@ -512,7 +576,7 @@ extension AppFlowyDatabaseTest on WidgetTester { Future assertRowCountInGridPage(int num) async { final text = find.byWidgetPredicate( - (widget) => widget is FlowyText && widget.title == rowCountString(num), + (widget) => widget is FlowyText && widget.text == rowCountString(num), ); expect(text, findsOneWidget); } @@ -653,7 +717,7 @@ extension AppFlowyDatabaseTest on WidgetTester { final findLayoutButton = find.byWidgetPredicate( (widget) => widget is FlowyText && - widget.title == DatabaseSettingAction.showLayout.title(), + widget.text == DatabaseSettingAction.showLayout.title(), ); final button = find.descendant( @@ -667,7 +731,7 @@ extension AppFlowyDatabaseTest on WidgetTester { Future selectDatabaseLayoutType(DatabaseLayoutPB layout) async { final findLayoutCell = find.byType(DatabaseViewLayoutCell); final findText = find.byWidgetPredicate( - (widget) => widget is FlowyText && widget.title == layout.layoutName(), + (widget) => widget is FlowyText && widget.text == layout.layoutName(), ); final button = find.descendant( diff --git a/frontend/appflowy_flutter/integration_test/util/expectation.dart b/frontend/appflowy_flutter/integration_test/util/expectation.dart index 5b3ddbf8d5..7c22494631 100644 --- a/frontend/appflowy_flutter/integration_test/util/expectation.dart +++ b/frontend/appflowy_flutter/integration_test/util/expectation.dart @@ -12,7 +12,7 @@ extension Expectation on WidgetTester { /// Expect to see the home page and with a default read me page. void expectToSeeHomePage() { expect(find.byType(HomeStack), findsOneWidget); - expect(find.textContaining(readme), findsOneWidget); + expect(find.textContaining(readme), findsWidgets); } /// Expect to see the page name on the home page. @@ -42,7 +42,7 @@ extension Expectation on WidgetTester { final exportSuccess = find.byWidgetPredicate( (widget) => widget is FlowyText && - widget.title == LocaleKeys.settings_files_exportFileSuccess.tr(), + widget.text == LocaleKeys.settings_files_exportFileSuccess.tr(), ); expect(exportSuccess, findsOneWidget); } @@ -62,7 +62,7 @@ extension Expectation on WidgetTester { /// Expect to see the user name on the home page void expectToSeeUserName(String name) { final userName = find.byWidgetPredicate( - (widget) => widget is FlowyText && widget.title == name, + (widget) => widget is FlowyText && widget.text == name, ); expect(userName, findsOneWidget); } @@ -72,7 +72,7 @@ extension Expectation on WidgetTester { Finder textWidget = find.textContaining(text, findRichText: true); if (textWidget.evaluate().isEmpty) { textWidget = find.byWidgetPredicate( - (widget) => widget is FlowyText && widget.title == text, + (widget) => widget is FlowyText && widget.text == text, ); } expect(textWidget, findsOneWidget); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_cell_action_sheet.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_cell_action_sheet.dart index d7ba6dbf80..19f2ecc24c 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_cell_action_sheet.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_cell_action_sheet.dart @@ -169,17 +169,14 @@ class FieldActionCell extends StatelessWidget { Widget build(BuildContext context) { return FlowyButton( hoverColor: AFThemeExtension.of(context).lightGreyHover, + disable: !enable, text: FlowyText.medium( action.title(), color: enable ? AFThemeExtension.of(context).textColor : Theme.of(context).disabledColor, ), - onTap: () { - if (enable) { - action.run(context, fieldInfo); - } - }, + onTap: () => action.run(context, fieldInfo), leftIcon: svgWidget( action.iconName(), color: enable diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_editor.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_editor.dart index 09bbf0de99..49d1f59014 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_editor.dart @@ -3,6 +3,7 @@ import 'package:appflowy/plugins/database_view/application/field/type_option/typ import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:dartz/dartz.dart' show none; import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/image.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/style_widget/text_field.dart'; @@ -232,15 +233,16 @@ class _DeleteFieldButton extends StatelessWidget { LocaleKeys.grid_field_delete.tr(), color: enable ? null : Theme.of(context).disabledColor, ), + leftIcon: svgWidget( + 'grid/delete', + color: enable ? null : Theme.of(context).disabledColor, + ), onTap: () { if (enable) onDeleted?.call(); }, onHover: (_) => popoverMutex.close(), ); - return Padding( - padding: const EdgeInsets.only(bottom: 4.0), - child: SizedBox(height: GridSize.popoverItemHeight, child: button), - ); + return SizedBox(height: GridSize.popoverItemHeight, child: button); }, ); } @@ -265,13 +267,11 @@ class _HideFieldButton extends StatelessWidget { text: FlowyText.medium( LocaleKeys.grid_field_hide.tr(), ), + leftIcon: svgWidget('grid/hide'), onTap: () => onHidden?.call(), onHover: (_) => popoverMutex.close(), ); - return Padding( - padding: const EdgeInsets.only(bottom: 4.0), - child: SizedBox(height: GridSize.popoverItemHeight, child: button), - ); + return SizedBox(height: GridSize.popoverItemHeight, child: button); }, ); } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_type_option_editor.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_type_option_editor.dart index d7bb2cf1f8..9b9d792933 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_type_option_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_type_option_editor.dart @@ -115,7 +115,6 @@ class SwitchFieldButton extends StatelessWidget { text: FlowyText.medium( bloc.state.field.fieldType.title(), ), - margin: GridSize.typeOptionContentInsets, leftIcon: FlowySvg(name: bloc.state.field.fieldType.iconName()), rightIcon: const FlowySvg(name: 'grid/more'), ); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/date.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/date.dart index 0d24db12a7..da81368c50 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/date.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/date.dart @@ -93,11 +93,9 @@ class DateTypeOptionWidget extends TypeOptionWidget { }, ); }, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), - child: DateFormatButton( - buttonMargins: GridSize.typeOptionContentInsets, - ), + child: const Padding( + padding: EdgeInsets.symmetric(horizontal: 12.0), + child: DateFormatButton(), ), ); } @@ -125,10 +123,7 @@ class DateTypeOptionWidget extends TypeOptionWidget { }, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12.0), - child: TimeFormatButton( - timeFormat: timeFormat, - buttonMargins: GridSize.typeOptionContentInsets, - ), + child: TimeFormatButton(timeFormat: timeFormat), ), ); } @@ -137,11 +132,9 @@ class DateTypeOptionWidget extends TypeOptionWidget { class DateFormatButton extends StatelessWidget { final VoidCallback? onTap; final void Function(bool)? onHover; - final EdgeInsets? buttonMargins; const DateFormatButton({ this.onTap, this.onHover, - this.buttonMargins, Key? key, }) : super(key: key); @@ -151,7 +144,6 @@ class DateFormatButton extends StatelessWidget { height: GridSize.popoverItemHeight, child: FlowyButton( text: FlowyText.medium(LocaleKeys.grid_field_dateFormat.tr()), - margin: buttonMargins, onTap: onTap, onHover: onHover, rightIcon: const FlowySvg(name: 'grid/more'), @@ -164,12 +156,10 @@ class TimeFormatButton extends StatelessWidget { final TimeFormatPB timeFormat; final VoidCallback? onTap; final void Function(bool)? onHover; - final EdgeInsets? buttonMargins; const TimeFormatButton({ required this.timeFormat, this.onTap, this.onHover, - this.buttonMargins, Key? key, }) : super(key: key); @@ -179,7 +169,6 @@ class TimeFormatButton extends StatelessWidget { height: GridSize.popoverItemHeight, child: FlowyButton( text: FlowyText.medium(LocaleKeys.grid_field_timeFormat.tr()), - margin: buttonMargins, onTap: onTap, onHover: onHover, rightIcon: const FlowySvg(name: 'grid/more'), diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/select_option.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/select_option.dart index 4c21a4a76f..c7fb5bab13 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/select_option.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/select_option.dart @@ -209,7 +209,7 @@ class _OptionCellState extends State<_OptionCell> { offset: const Offset(8, 0), margin: EdgeInsets.zero, asBarrier: true, - constraints: BoxConstraints.loose(const Size(460, 460)), + constraints: BoxConstraints.loose(const Size(460, 470)), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12.0), child: child, diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/select_option_editor.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/select_option_editor.dart index c5b40b22ec..d0d573783d 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/select_option_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/select_option_editor.dart @@ -337,7 +337,7 @@ class _SelectOptionCellState extends State<_SelectOptionCell> { offset: const Offset(8, 0), margin: EdgeInsets.zero, asBarrier: true, - constraints: BoxConstraints.loose(const Size(200, 460)), + constraints: BoxConstraints.loose(const Size(200, 470)), mutex: widget.popoverMutex, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12.0), diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart index 65327d52df..258d7eddc6 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart @@ -56,7 +56,13 @@ class FlowyButton extends StatelessWidget { ), ); } else { - return Opacity(opacity: disableOpacity, child: _render()); + return Opacity( + opacity: disableOpacity, + child: MouseRegion( + cursor: SystemMouseCursors.forbidden, + child: _render(), + ), + ); } } diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart index af317d53f5..e90b45fdf8 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; class FlowyText extends StatelessWidget { - final String title; + final String text; final TextOverflow? overflow; final double? fontSize; final FontWeight? fontWeight; @@ -13,8 +13,7 @@ class FlowyText extends StatelessWidget { final String? fontFamily; const FlowyText( - this.title, { - Key? key, + this.text, { this.overflow = TextOverflow.clip, this.fontSize, this.fontWeight, @@ -24,11 +23,11 @@ class FlowyText extends StatelessWidget { this.decoration, this.selectable = false, this.fontFamily, + Key? key, }) : super(key: key); const FlowyText.regular( - this.title, { - Key? key, + this.text, { this.fontSize, this.overflow, this.color, @@ -37,12 +36,12 @@ class FlowyText extends StatelessWidget { this.decoration, this.selectable = false, this.fontFamily, + Key? key, }) : fontWeight = FontWeight.w400, super(key: key); const FlowyText.medium( - this.title, { - Key? key, + this.text, { this.fontSize, this.overflow, this.color, @@ -51,12 +50,12 @@ class FlowyText extends StatelessWidget { this.decoration, this.selectable = false, this.fontFamily, + Key? key, }) : fontWeight = FontWeight.w500, super(key: key); const FlowyText.semibold( - this.title, { - Key? key, + this.text, { this.fontSize, this.overflow, this.color, @@ -65,14 +64,12 @@ class FlowyText extends StatelessWidget { this.decoration, this.selectable = false, this.fontFamily, + Key? key, }) : fontWeight = FontWeight.w600, super(key: key); @override Widget build(BuildContext context) { - final text = overflow == TextOverflow.ellipsis - ? title.replaceAll('', '\u200B') - : title; if (selectable) { return SelectableText( text,