Selaa lähdekoodia

chore: some ui improvements (#2791)

* chore: some ui improvements

* fix: integration test

* feat: language selector on welcome page (#2796)

* feat: add language selector on welcome page

* feat: add hover effect and refactor layout

* test: add basic languge selector testing

* chore: increate place holder width

* fix: add catch error for setLocale and finish the testing

* chore: update comment

* feat: refactor the skip login in page and add tests

---------

Co-authored-by: Lucas.Xu <[email protected]>

* feat: row document (#2792)

* chore: create orphan view handler

* feat: save icon url and cover url in view

* feat: implement emoji picker UI

* chore: config ui

* chore: config ui again

* chore: replace RowPB with RowMetaPB to exposing more row information

* fix: compile error

* feat: show emoji in row

* chore: update

* test: insert emoji test

* test: add update emoji test

* test: add remove emoji test

* test: add create field tests

* test: add create row and delete row integration tests

* test: add create row from row menu

* test: document in row detail page

* test: delete, duplicate row in row detail page

* test: check the row count displayed in grid page

* test: rename existing field in grid page

* test: update field type of exisiting field in grid page

* test: delete field test

* test: add duplicate field test

* test: add hide field test

* test: add edit text cell test

* test: add insert text to text cell test

* test: add edit number cell test

* test: add edit multiple number cells

* test: add edit checkbox cell test

* feat: integrate editor into database row

* test: add edit create time and last edit time cell test

* test: add edit date cell by selecting a date test

* chore: remove unused code

* chore: update checklist bg color

* test: add update database layout test

---------

Co-authored-by: Lucas.Xu <[email protected]>

* test: fix test

* test: add create select option test

---------

Co-authored-by: Yijing Huang <[email protected]>
Co-authored-by: Lucas.Xu <[email protected]>
Co-authored-by: Nathan.fooo <[email protected]>
Co-authored-by: nathan <[email protected]>
Richard Shiue 1 vuosi sitten
vanhempi
commit
efc857d752

+ 25 - 0
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();
+    });
   });
 }

+ 2 - 2
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);
     });

+ 2 - 2
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,
     );
   }
 }

+ 72 - 8
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<void> 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<void> 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<void> 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<void> openFirstRowDetailPage() async {
     await hoverOnFirstRowOfGrid();
 
@@ -410,11 +465,10 @@ extension AppFlowyDatabaseTest on WidgetTester {
   /// Must call [tapTypeOptionButton] first.
   Future<void> 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<void> findSelectOptionEditor(dynamic matcher) async {
+    final finder = find.byType(SelectOptionCellEditor);
+    expect(finder, matcher);
+  }
+
+  Future<void> dismissSelectOptionEditor() async {
+    await sendKeyEvent(LogicalKeyboardKey.escape);
+    await pumpAndSettle();
+  }
+
   Future<void> tapCreateRowButtonInGrid() async {
     await tapButton(find.byType(GridAddRowButton));
   }
@@ -512,7 +576,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
 
   Future<void> 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<void> 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(

+ 4 - 4
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);

+ 2 - 5
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

+ 8 - 8
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);
       },
     );
   }

+ 0 - 1
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'),
     );

+ 4 - 15
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'),

+ 1 - 1
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,

+ 1 - 1
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),

+ 7 - 1
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(),
+        ),
+      );
     }
   }
 

+ 9 - 12
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,