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 <lucas.xu@appflowy.io>

* 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 <lucas.xu@appflowy.io>

* test: fix test

* test: add create select option test

---------

Co-authored-by: Yijing Huang <hyj891204@gmail.com>
Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
Co-authored-by: Nathan.fooo <86001920+appflowy@users.noreply.github.com>
Co-authored-by: nathan <nathan@appflowy.io>
This commit is contained in:
Richard Shiue 2023-06-16 15:32:28 +08:00 committed by GitHub
parent 14dee6b797
commit efc857d752
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 137 additions and 60 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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