From f3aaff77b9e72c062dd92556e3a59a8662ae28eb Mon Sep 17 00:00:00 2001 From: Richard Shiue <71320345+richardshiue@users.noreply.github.com> Date: Sat, 2 Sep 2023 00:42:46 +0800 Subject: [PATCH] chore: per-view field visibility UI (#3296) * chore: default field settings if not exist * chore: field settings listeners and services * chore: don't need to updateFieldInfos * feat: per-view field visibilty UI * fix: remove unresolved imports --- .../database_field_settings_test.dart | 55 +++++++++++ .../integration_test/database_field_test.dart | 20 ---- .../integration_test/runner.dart | 2 + .../util/database_test_op.dart | 52 +++++++++++ .../field/field_action_sheet_bloc.dart | 19 +++- .../application/field/field_controller.dart | 13 +-- .../application/field/field_service.dart | 5 - .../application/row/row_cache.dart | 3 +- .../application/setting/property_bloc.dart | 12 ++- .../grid/application/grid_header_bloc.dart | 18 ++-- .../grid/application/row/row_detail_bloc.dart | 36 ++++++-- .../widgets/header/field_cell.dart | 7 +- .../widgets/header/field_editor.dart | 7 +- .../widgets/header/grid_header.dart | 7 +- .../widgets/field/grid_property.dart | 92 +++++++++++-------- .../database_view/widgets/row/row_detail.dart | 2 +- .../database/mock_data/board_mock_data.rs | 1 - 17 files changed, 241 insertions(+), 110 deletions(-) create mode 100644 frontend/appflowy_flutter/integration_test/database_field_settings_test.dart diff --git a/frontend/appflowy_flutter/integration_test/database_field_settings_test.dart b/frontend/appflowy_flutter/integration_test/database_field_settings_test.dart new file mode 100644 index 0000000000..b229938211 --- /dev/null +++ b/frontend/appflowy_flutter/integration_test/database_field_settings_test.dart @@ -0,0 +1,55 @@ +import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart'; +import 'package:appflowy/plugins/database_view/tar_bar/tar_bar_add_button.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder2/view.pbenum.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import 'util/database_test_op.dart'; +import 'util/util.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('database field settings', () { + testWidgets('field visibility', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapGoButton(); + + await tester.createNewPageWithName(layout: ViewLayoutPB.Grid); + await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.grid); + + // create a field + await tester.scrollToRight(find.byType(GridPage)); + await tester.tapNewPropertyButton(); + await tester.renameField('New field 1'); + await tester.dismissFieldEditor(); + + // hide the field + await tester.tapGridFieldWithName('New field 1'); + await tester.tapHidePropertyButton(); + await tester.noFieldWithName('New field 1'); + + // go back to inline database view, expect field to be shown + await tester.tapTabBarLinkedViewByViewName('Untitled'); + await tester.findFieldWithName('New field 1'); + + // go back to linked database view, expect field to be hidden + await tester.tapTabBarLinkedViewByViewName('grid'); + await tester.noFieldWithName('New field 1'); + + // use the settings button to show the field + await tester.tapDatabaseSettingButton(); + await tester.tapViewPropertiesButton(); + await tester.tapViewTogglePropertyVisibilityButtonByName('New field 1'); + await tester.dismissFieldEditor(); + await tester.findFieldWithName('New field 1'); + + // open first row in popup then hide the field + await tester.openFirstRowDetailPage(); + await tester.tapGridFieldWithNameInRowDetailPage('New field 1'); + await tester.tapHidePropertyButtonInFieldEditor(); + await tester.dismissRowDetailPage(); + await tester.noFieldWithName('New field 1'); + }); + }); +} diff --git a/frontend/appflowy_flutter/integration_test/database_field_test.dart b/frontend/appflowy_flutter/integration_test/database_field_test.dart index 94a382332a..01492364a4 100644 --- a/frontend/appflowy_flutter/integration_test/database_field_test.dart +++ b/frontend/appflowy_flutter/integration_test/database_field_test.dart @@ -106,26 +106,6 @@ void main() { await tester.pumpAndSettle(); }); - testWidgets('hide field', (tester) async { - await tester.initializeAppFlowy(); - await tester.tapGoButton(); - - await tester.createNewPageWithName(layout: ViewLayoutPB.Grid); - - // create a field - await tester.scrollToRight(find.byType(GridPage)); - await tester.tapNewPropertyButton(); - await tester.renameField('New field 1'); - await tester.dismissFieldEditor(); - - // Delete the field - await tester.tapGridFieldWithName('New field 1'); - await tester.tapHidePropertyButton(); - - await tester.noFieldWithName('New field 1'); - await tester.pumpAndSettle(); - }); - testWidgets('create checklist field ', (tester) async { await tester.initializeAppFlowy(); await tester.tapGoButton(); diff --git a/frontend/appflowy_flutter/integration_test/runner.dart b/frontend/appflowy_flutter/integration_test/runner.dart index a692acc76f..3a34c2c611 100644 --- a/frontend/appflowy_flutter/integration_test/runner.dart +++ b/frontend/appflowy_flutter/integration_test/runner.dart @@ -4,6 +4,7 @@ import 'package:integration_test/integration_test.dart'; import 'database_calendar_test.dart' as database_calendar_test; import 'database_cell_test.dart' as database_cell_test; import 'database_field_test.dart' as database_field_test; +import 'database_field_settings_test.dart' as database_field_settings_test; import 'database_filter_test.dart' as database_filter_test; import 'database_row_page_test.dart' as database_row_page_test; import 'database_row_test.dart' as database_row_test; @@ -47,6 +48,7 @@ void main() { // Database integration tests database_cell_test.main(); database_field_test.main(); + database_field_settings_test.main(); database_share_test.main(); database_row_page_test.main(); database_row_test.main(); 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 982f17cedd..be34b62610 100644 --- a/frontend/appflowy_flutter/integration_test/util/database_test_op.dart +++ b/frontend/appflowy_flutter/integration_test/util/database_test_op.dart @@ -35,6 +35,7 @@ import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar import 'package:appflowy/plugins/database_view/tar_bar/tab_bar_header.dart'; import 'package:appflowy/plugins/database_view/tar_bar/tar_bar_add_button.dart'; import 'package:appflowy/plugins/database_view/widgets/database_layout_ext.dart'; +import 'package:appflowy/plugins/database_view/widgets/field/grid_property.dart'; import 'package:appflowy/plugins/database_view/widgets/row/accessory/cell_accessory.dart'; import 'package:appflowy/plugins/database_view/widgets/row/cells/cells.dart'; import 'package:appflowy/plugins/database_view/widgets/row/cells/checklist_cell/checklist_progress_bar.dart'; @@ -503,6 +504,18 @@ extension AppFlowyDatabaseTest on WidgetTester { await tapButton(findDateCell); } + Future tapGridFieldWithNameInRowDetailPage(String name) async { + final fields = find.byWidgetPredicate( + (widget) => widget is FieldCellButton && widget.field.name == name, + ); + final field = find.descendant( + of: find.byType(RowDetailPage), + matching: fields, + ); + await tapButton(field); + await pumpAndSettle(); + } + Future duplicateRowInRowDetailPage() async { final duplicateButton = find.byType(RowDetailPageDuplicateButton); await tapButton(duplicateButton); @@ -585,6 +598,11 @@ extension AppFlowyDatabaseTest on WidgetTester { await tapButton(field); } + Future tapHidePropertyButtonInFieldEditor() async { + final button = find.byType(HideFieldButton); + await tapButton(button); + } + Future tapRowDetailPageCreatePropertyButton() async { await tapButton(find.byType(CreateRowFieldButton)); } @@ -925,6 +943,23 @@ extension AppFlowyDatabaseTest on WidgetTester { await tapButton(button); } + /// Should call [tapDatabaseSettingButton] first. + Future tapViewPropertiesButton() async { + final findSettingItem = find.byType(DatabaseSettingItem); + final findLayoutButton = find.byWidgetPredicate( + (widget) => + widget is FlowyText && + widget.text == DatabaseSettingAction.showProperties.title(), + ); + + final button = find.descendant( + of: findSettingItem, + matching: findLayoutButton, + ); + + await tapButton(button); + } + /// Should call [tapDatabaseSettingButton] first. Future tapDatabaseLayoutButton() async { final findSettingItem = find.byType(DatabaseSettingItem); @@ -1111,6 +1146,11 @@ extension AppFlowyDatabaseTest on WidgetTester { await tapButton(findCreateButton); } + Future tapTabBarLinkedViewByViewName(String name) async { + final viewButton = findTabBarLinkViewByViewName(name); + await tapButton(viewButton); + } + Finder findTabBarLinkViewByViewLayout(ViewLayoutPB layout) { return find.byWidgetPredicate( (widget) => widget is TabBarItemButton && widget.view.layout == layout, @@ -1212,6 +1252,18 @@ extension AppFlowyDatabaseTest on WidgetTester { Future tapAddSelectOptionButton() async { await tapButtonWithName(LocaleKeys.grid_field_addSelectOption.tr()); } + + Future tapViewTogglePropertyVisibilityButtonByName( + String fieldName, + ) async { + final field = find.byWidgetPredicate( + (widget) => + widget is GridPropertyCell && widget.fieldInfo.name == fieldName, + ); + final toggleVisibilityButton = + find.descendant(of: field, matching: find.byType(FlowyIconButton)); + await tapButton(toggleVisibilityButton); + } } Finder finderForDatabaseLayoutType(DatabaseLayoutPB layout) { diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_action_sheet_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_action_sheet_bloc.dart index 7b50c44417..952006215e 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_action_sheet_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_action_sheet_bloc.dart @@ -1,5 +1,7 @@ +import 'package:appflowy/plugins/database_view/application/field_settings/field_settings_service.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'field_service.dart'; @@ -8,13 +10,18 @@ part 'field_action_sheet_bloc.freezed.dart'; class FieldActionSheetBloc extends Bloc { + final String fieldId; final FieldBackendService fieldService; + final FieldSettingsBackendService fieldSettingsService; FieldActionSheetBloc({required FieldContext fieldCellContext}) - : fieldService = FieldBackendService( + : fieldId = fieldCellContext.field.id, + fieldService = FieldBackendService( viewId: fieldCellContext.viewId, fieldId: fieldCellContext.field.id, ), + fieldSettingsService = + FieldSettingsBackendService(viewId: fieldCellContext.viewId), super( FieldActionSheetState.initial( TypeOptionPB.create()..field_2 = fieldCellContext.field, @@ -31,14 +38,20 @@ class FieldActionSheetBloc ); }, hideField: (_HideField value) async { - final result = await fieldService.updateField(visibility: false); + final result = await fieldSettingsService.updateFieldSettings( + fieldId: fieldId, + fieldVisibility: FieldVisibility.AlwaysHidden, + ); result.fold( (l) => null, (err) => Log.error(err), ); }, showField: (_ShowField value) async { - final result = await fieldService.updateField(visibility: true); + final result = await fieldSettingsService.updateFieldSettings( + fieldId: fieldId, + fieldVisibility: FieldVisibility.AlwaysShown, + ); result.fold( (l) => null, (err) => Log.error(err), diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_controller.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_controller.dart index 0e464e33fe..6faf52aab5 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_controller.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_controller.dart @@ -417,7 +417,7 @@ class FieldController { _fieldNotifier.fieldInfos = newFieldInfos; } - List updateFields(List updatedFieldPBs) { + Future> updateFields(List updatedFieldPBs) async { if (updatedFieldPBs.isEmpty) { return []; } @@ -429,7 +429,8 @@ class FieldController { newFields.indexWhere((field) => field.id == updatedFieldPB.id); if (index != -1) { newFields.removeAt(index); - final fieldInfo = FieldInfo.initial(updatedFieldPB); + final initial = FieldInfo.initial(updatedFieldPB); + final fieldInfo = await attachFieldSettings(initial); newFields.insert(index, fieldInfo); updatedFields.add(fieldInfo); } @@ -443,16 +444,16 @@ class FieldController { // Listen on field's changes _fieldListener.start( - onFieldsChanged: (result) { + onFieldsChanged: (result) async { result.fold( - (changeset) { + (changeset) async { if (_isDisposed) { return; } deleteFields(changeset.deletedFields); insertFields(changeset.insertedFields); - final updatedFields = updateFields(changeset.updatedFields); + final updatedFields = await updateFields(changeset.updatedFields); for (final listener in _updatedFieldCallbacks.values) { listener(updatedFields); } @@ -548,7 +549,7 @@ class FieldController { _fieldNotifier.fieldInfos = newFields.map((field) => FieldInfo.initial(field)).toList(); - Future.wait([ + await Future.wait([ _loadFilters(), _loadSorts(), _loadAllFieldSettings(), diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_service.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_service.dart index 05b1b30e71..953da61162 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_service.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_service.dart @@ -30,7 +30,6 @@ class FieldBackendService { Future> updateField({ String? name, bool? frozen, - bool? visibility, double? width, }) { final payload = FieldChangesetPB.create() @@ -45,10 +44,6 @@ class FieldBackendService { payload.frozen = frozen; } - if (visibility != null) { - payload.visibility = visibility; - } - if (width != null) { payload.width = width.toInt(); } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/row/row_cache.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/row/row_cache.dart index 005252cf71..b1acb8f734 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/application/row/row_cache.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/row/row_cache.dart @@ -246,7 +246,8 @@ class RowCache { // ignore: prefer_collection_literals final cellContextMap = CellContextByFieldId(); for (final fieldInfo in _fieldDelegate.fieldInfos) { - if (fieldInfo.field.visibility) { + if (fieldInfo.visibility != null && + fieldInfo.visibility != FieldVisibility.AlwaysHidden) { cellContextMap[fieldInfo.id] = DatabaseCellContext( rowMeta: rowMeta, viewId: viewId, diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/setting/property_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/setting/property_bloc.dart index 31554c652c..bbef6db894 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/application/setting/property_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/setting/property_bloc.dart @@ -1,6 +1,8 @@ import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; import 'package:appflowy/plugins/database_view/application/field/field_info.dart'; +import 'package:appflowy/plugins/database_view/application/field_settings/field_settings_service.dart'; import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; @@ -28,13 +30,13 @@ class DatabasePropertyBloc _startListening(); }, setFieldVisibility: (_SetFieldVisibility value) async { - final fieldBackendSvc = FieldBackendService( + final fieldSettingsSvc = FieldSettingsBackendService( viewId: viewId, - fieldId: value.fieldId, ); - final result = await fieldBackendSvc.updateField( - visibility: value.visibility, + final result = await fieldSettingsSvc.updateFieldSettings( + fieldId: value.fieldId, + fieldVisibility: value.visibility, ); result.fold((l) => null, (err) => Log.error(err)); @@ -84,7 +86,7 @@ class DatabasePropertyEvent with _$DatabasePropertyEvent { const factory DatabasePropertyEvent.initial() = _Initial; const factory DatabasePropertyEvent.setFieldVisibility( String fieldId, - bool visibility, + FieldVisibility visibility, ) = _SetFieldVisibility; const factory DatabasePropertyEvent.didReceiveFieldUpdate( List fields, diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/grid_header_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/grid_header_bloc.dart index e1c9a363fe..173a74bc87 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/grid_header_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/grid_header_bloc.dart @@ -2,6 +2,7 @@ import 'package:appflowy/plugins/database_view/application/field/field_controlle import 'package:appflowy/plugins/database_view/application/field/field_info.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; @@ -16,18 +17,25 @@ class GridHeaderBloc extends Bloc { GridHeaderBloc({ required this.viewId, required this.fieldController, - }) : super(GridHeaderState.initial(fieldController.fieldInfos)) { + }) : super(GridHeaderState.initial()) { on( (event, emit) async { await event.map( initial: (_InitialHeader value) async { _startListening(); + add( + GridHeaderEvent.didReceiveFieldUpdate(fieldController.fieldInfos), + ); }, didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) { emit( state.copyWith( fields: value.fields - .where((element) => element.field.visibility) + .where( + (element) => + element.visibility != null && + element.visibility != FieldVisibility.AlwaysHidden, + ) .toList(), ), ); @@ -83,9 +91,5 @@ class GridHeaderState with _$GridHeaderState { const factory GridHeaderState({required List fields}) = _GridHeaderState; - factory GridHeaderState.initial(List fields) { - // final List newFields = List.from(fields); - // newFields.retainWhere((field) => field.visibility); - return GridHeaderState(fields: fields); - } + factory GridHeaderState.initial() => const GridHeaderState(fields: []); } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/row/row_detail_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/row/row_detail_bloc.dart index 4cd382afc5..d37e3ac026 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/row/row_detail_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/row/row_detail_bloc.dart @@ -1,5 +1,7 @@ +import 'package:appflowy/plugins/database_view/application/field_settings/field_settings_service.dart'; import 'package:appflowy/plugins/database_view/application/row/row_service.dart'; import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; @@ -10,18 +12,18 @@ part 'row_detail_bloc.freezed.dart'; class RowDetailBloc extends Bloc { final RowBackendService rowService; - final RowController dataController; + final RowController rowController; RowDetailBloc({ - required this.dataController, - }) : rowService = RowBackendService(viewId: dataController.viewId), + required this.rowController, + }) : rowService = RowBackendService(viewId: rowController.viewId), super(RowDetailState.initial()) { on( (event, emit) async { await event.when( initial: () async { await _startListening(); - final cells = dataController.loadData(); + final cells = rowController.loadData(); if (!isClosed) { add(RowDetailEvent.didReceiveCellDatas(cells.values.toList())); } @@ -32,9 +34,24 @@ class RowDetailBloc extends Bloc { deleteField: (fieldId) { _fieldBackendService(fieldId).deleteField(); }, + showField: (fieldId) async { + final result = + await FieldSettingsBackendService(viewId: rowController.viewId) + .updateFieldSettings( + fieldId: fieldId, + fieldVisibility: FieldVisibility.AlwaysShown, + ); + result.fold( + (l) {}, + (err) => Log.error(err), + ); + }, hideField: (fieldId) async { - final result = await _fieldBackendService(fieldId).updateField( - visibility: false, + final result = + await FieldSettingsBackendService(viewId: rowController.viewId) + .updateFieldSettings( + fieldId: fieldId, + fieldVisibility: FieldVisibility.AlwaysHidden, ); result.fold( (l) {}, @@ -57,12 +74,12 @@ class RowDetailBloc extends Bloc { @override Future close() async { - dataController.dispose(); + rowController.dispose(); return super.close(); } Future _startListening() async { - dataController.addListener( + rowController.addListener( onRowChanged: (cells, reason) { if (!isClosed) { add(RowDetailEvent.didReceiveCellDatas(cells.values.toList())); @@ -73,7 +90,7 @@ class RowDetailBloc extends Bloc { FieldBackendService _fieldBackendService(String fieldId) { return FieldBackendService( - viewId: dataController.viewId, + viewId: rowController.viewId, fieldId: fieldId, ); } @@ -83,6 +100,7 @@ class RowDetailBloc extends Bloc { class RowDetailEvent with _$RowDetailEvent { const factory RowDetailEvent.initial() = _Initial; const factory RowDetailEvent.deleteField(String fieldId) = _DeleteField; + const factory RowDetailEvent.showField(String fieldId) = _ShowField; const factory RowDetailEvent.hideField(String fieldId) = _HideField; const factory RowDetailEvent.deleteRow(String rowId) = _DeleteRow; const factory RowDetailEvent.duplicateRow(String rowId, String? groupId) = diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_cell.dart index 0efc95a613..8a232adf25 100755 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_cell.dart @@ -164,11 +164,6 @@ class FieldCellButton extends StatelessWidget { @override Widget build(BuildContext context) { - // Using this technique to have proper text ellipsis - // https://github.com/flutter/flutter/issues/18761#issuecomment-812390920 - final text = Characters(field.name) - .replaceAll(Characters(''), Characters('\u{200B}')) - .toString(); return FlowyButton( hoverColor: AFThemeExtension.of(context).greyHover, onTap: onTap, @@ -177,7 +172,7 @@ class FieldCellButton extends StatelessWidget { ), radius: radius, text: FlowyText.medium( - text, + field.name, maxLines: maxLines, overflow: TextOverflow.ellipsis, ), 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 57cca6acba..b04286c291 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 @@ -102,7 +102,7 @@ class _FieldEditorState extends State { builder: (context, state) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 12.0), - child: _HideFieldButton( + child: HideFieldButton( popoverMutex: popoverMutex, onHidden: () { state.field.fold( @@ -236,11 +236,12 @@ class _DeleteFieldButton extends StatelessWidget { } } -class _HideFieldButton extends StatelessWidget { +@visibleForTesting +class HideFieldButton extends StatelessWidget { final PopoverMutex popoverMutex; final VoidCallback? onHidden; - const _HideFieldButton({ + const HideFieldButton({ required this.popoverMutex, required this.onHidden, Key? key, diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/grid_header.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/grid_header.dart index 2069fd2598..b406263388 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/grid_header.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/grid_header.dart @@ -42,12 +42,10 @@ class _GridHeaderSliverAdaptorState extends State { Widget build(BuildContext context) { return BlocProvider( create: (context) { - final bloc = getIt( + return getIt( param1: widget.viewId, param2: widget.fieldController, - ); - bloc.add(const GridHeaderEvent.initial()); - return bloc; + )..add(const GridHeaderEvent.initial()); }, child: BlocBuilder( buildWhen: (previous, current) => @@ -97,7 +95,6 @@ class _GridHeaderState extends State<_GridHeader> { buildWhen: (previous, current) => previous.fields != current.fields, builder: (context, state) { final cells = state.fields - .where((fieldInfo) => fieldInfo.field.visibility) .map( (field) => FieldContext( viewId: widget.viewId, diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/field/grid_property.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/field/grid_property.dart index 28f9ab3a69..6a7b560b27 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/field/grid_property.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/field/grid_property.dart @@ -5,6 +5,7 @@ import 'package:appflowy/plugins/database_view/application/field/type_option/typ import 'package:appflowy/plugins/database_view/application/setting/property_bloc.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart'; import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flowy_infra/theme_extension.dart'; @@ -44,7 +45,7 @@ class _DatabasePropertyListState extends State { child: BlocBuilder( builder: (context, state) { final cells = state.fieldContexts.map((field) { - return _GridPropertyCell( + return GridPropertyCell( key: ValueKey(field.id), viewId: widget.viewId, fieldInfo: field, @@ -75,12 +76,13 @@ class _DatabasePropertyListState extends State { } } -class _GridPropertyCell extends StatefulWidget { +@visibleForTesting +class GridPropertyCell extends StatefulWidget { final FieldInfo fieldInfo; final String viewId; final PopoverMutex popoverMutex; - const _GridPropertyCell({ + const GridPropertyCell({ super.key, required this.fieldInfo, required this.viewId, @@ -88,26 +90,22 @@ class _GridPropertyCell extends StatefulWidget { }); @override - State<_GridPropertyCell> createState() => _GridPropertyCellState(); + State createState() => _GridPropertyCellState(); } -class _GridPropertyCellState extends State<_GridPropertyCell> { +class _GridPropertyCellState extends State { final PopoverController _popoverController = PopoverController(); @override Widget build(BuildContext context) { - final checkmark = FlowySvg( - widget.fieldInfo.field.visibility ? FlowySvgs.show_m : FlowySvgs.hide_m, + final visiblity = widget.fieldInfo.visibility; + final visibleIcon = FlowySvg( + visiblity != null && visiblity != FieldVisibility.AlwaysHidden + ? FlowySvgs.show_m + : FlowySvgs.hide_m, color: Theme.of(context).iconTheme.color, ); - return SizedBox( - height: GridSize.popoverItemHeight, - child: _editFieldButton(context, checkmark), - ); - } - - Widget _editFieldButton(BuildContext context, Widget checkmark) { return AppFlowyPopover( mutex: widget.popoverMutex, controller: _popoverController, @@ -116,30 +114,40 @@ class _GridPropertyCellState extends State<_GridPropertyCell> { constraints: BoxConstraints.loose(const Size(240, 400)), triggerActions: PopoverTriggerFlags.none, margin: EdgeInsets.zero, - child: FlowyButton( - hoverColor: AFThemeExtension.of(context).lightGreyHover, - text: FlowyText.medium( - widget.fieldInfo.name, - color: AFThemeExtension.of(context).textColor, - ), - leftIcon: FlowySvg( - widget.fieldInfo.fieldType.icon(), - color: Theme.of(context).iconTheme.color, - ), - rightIcon: FlowyIconButton( - hoverColor: Colors.transparent, - onPressed: () { - context.read().add( - DatabasePropertyEvent.setFieldVisibility( - widget.fieldInfo.id, - !widget.fieldInfo.field.visibility, - ), - ); - }, - icon: checkmark.padding(all: 6.0), - ), - onTap: () => _popoverController.show(), - ).padding(horizontal: 6.0), + child: SizedBox( + height: GridSize.popoverItemHeight, + child: FlowyButton( + hoverColor: AFThemeExtension.of(context).lightGreyHover, + text: FlowyText.medium( + widget.fieldInfo.name, + color: AFThemeExtension.of(context).textColor, + ), + leftIcon: FlowySvg( + widget.fieldInfo.fieldType.icon(), + color: Theme.of(context).iconTheme.color, + ), + rightIcon: FlowyIconButton( + hoverColor: Colors.transparent, + onPressed: () { + if (widget.fieldInfo.fieldSettings == null) { + return; + } + + final newVisiblity = _newFieldVisibility( + widget.fieldInfo.fieldSettings!.visibility, + ); + context.read().add( + DatabasePropertyEvent.setFieldVisibility( + widget.fieldInfo.id, + newVisiblity, + ), + ); + }, + icon: visibleIcon.padding(all: 6.0), + ), + onTap: () => _popoverController.show(), + ).padding(horizontal: 6.0), + ), popupBuilder: (BuildContext context) { return FieldEditor( viewId: widget.viewId, @@ -151,4 +159,12 @@ class _GridPropertyCellState extends State<_GridPropertyCell> { }, ); } + + FieldVisibility _newFieldVisibility(FieldVisibility current) { + return switch (current) { + FieldVisibility.AlwaysShown => FieldVisibility.AlwaysHidden, + FieldVisibility.AlwaysHidden => FieldVisibility.AlwaysShown, + _ => FieldVisibility.AlwaysHidden, + }; + } } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_detail.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_detail.dart index 89ad893057..ac6cca1388 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_detail.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_detail.dart @@ -48,7 +48,7 @@ class _RowDetailPageState extends State { return FlowyDialog( child: BlocProvider( create: (context) { - return RowDetailBloc(dataController: widget.rowController) + return RowDetailBloc(rowController: widget.rowController) ..add(const RowDetailEvent.initial()); }, child: ListView( diff --git a/frontend/rust-lib/flowy-database2/tests/database/mock_data/board_mock_data.rs b/frontend/rust-lib/flowy-database2/tests/database/mock_data/board_mock_data.rs index 83cbf31130..8ffbf8d7b0 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/mock_data/board_mock_data.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/mock_data/board_mock_data.rs @@ -17,7 +17,6 @@ use crate::database::mock_data::{COMPLETED, FACEBOOK, GOOGLE, PAUSED, PLANNED, T pub fn make_test_board() -> DatabaseData { let mut fields = vec![]; let mut rows = vec![]; - // Iterate through the FieldType to create the corresponding Field. for field_type in FieldType::iter() { match field_type {