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
This commit is contained in:
Richard Shiue 2023-09-02 00:42:46 +08:00 committed by GitHub
parent 9b7ff375b2
commit f3aaff77b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 241 additions and 110 deletions

View File

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

View File

@ -106,26 +106,6 @@ void main() {
await tester.pumpAndSettle(); 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 { testWidgets('create checklist field ', (tester) async {
await tester.initializeAppFlowy(); await tester.initializeAppFlowy();
await tester.tapGoButton(); await tester.tapGoButton();

View File

@ -4,6 +4,7 @@ import 'package:integration_test/integration_test.dart';
import 'database_calendar_test.dart' as database_calendar_test; import 'database_calendar_test.dart' as database_calendar_test;
import 'database_cell_test.dart' as database_cell_test; import 'database_cell_test.dart' as database_cell_test;
import 'database_field_test.dart' as database_field_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_filter_test.dart' as database_filter_test;
import 'database_row_page_test.dart' as database_row_page_test; import 'database_row_page_test.dart' as database_row_page_test;
import 'database_row_test.dart' as database_row_test; import 'database_row_test.dart' as database_row_test;
@ -47,6 +48,7 @@ void main() {
// Database integration tests // Database integration tests
database_cell_test.main(); database_cell_test.main();
database_field_test.main(); database_field_test.main();
database_field_settings_test.main();
database_share_test.main(); database_share_test.main();
database_row_page_test.main(); database_row_page_test.main();
database_row_test.main(); database_row_test.main();

View File

@ -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/tab_bar_header.dart';
import 'package:appflowy/plugins/database_view/tar_bar/tar_bar_add_button.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/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/accessory/cell_accessory.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/cells.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'; 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); await tapButton(findDateCell);
} }
Future<void> 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<void> duplicateRowInRowDetailPage() async { Future<void> duplicateRowInRowDetailPage() async {
final duplicateButton = find.byType(RowDetailPageDuplicateButton); final duplicateButton = find.byType(RowDetailPageDuplicateButton);
await tapButton(duplicateButton); await tapButton(duplicateButton);
@ -585,6 +598,11 @@ extension AppFlowyDatabaseTest on WidgetTester {
await tapButton(field); await tapButton(field);
} }
Future<void> tapHidePropertyButtonInFieldEditor() async {
final button = find.byType(HideFieldButton);
await tapButton(button);
}
Future<void> tapRowDetailPageCreatePropertyButton() async { Future<void> tapRowDetailPageCreatePropertyButton() async {
await tapButton(find.byType(CreateRowFieldButton)); await tapButton(find.byType(CreateRowFieldButton));
} }
@ -925,6 +943,23 @@ extension AppFlowyDatabaseTest on WidgetTester {
await tapButton(button); await tapButton(button);
} }
/// Should call [tapDatabaseSettingButton] first.
Future<void> 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. /// Should call [tapDatabaseSettingButton] first.
Future<void> tapDatabaseLayoutButton() async { Future<void> tapDatabaseLayoutButton() async {
final findSettingItem = find.byType(DatabaseSettingItem); final findSettingItem = find.byType(DatabaseSettingItem);
@ -1111,6 +1146,11 @@ extension AppFlowyDatabaseTest on WidgetTester {
await tapButton(findCreateButton); await tapButton(findCreateButton);
} }
Future<void> tapTabBarLinkedViewByViewName(String name) async {
final viewButton = findTabBarLinkViewByViewName(name);
await tapButton(viewButton);
}
Finder findTabBarLinkViewByViewLayout(ViewLayoutPB layout) { Finder findTabBarLinkViewByViewLayout(ViewLayoutPB layout) {
return find.byWidgetPredicate( return find.byWidgetPredicate(
(widget) => widget is TabBarItemButton && widget.view.layout == layout, (widget) => widget is TabBarItemButton && widget.view.layout == layout,
@ -1212,6 +1252,18 @@ extension AppFlowyDatabaseTest on WidgetTester {
Future<void> tapAddSelectOptionButton() async { Future<void> tapAddSelectOptionButton() async {
await tapButtonWithName(LocaleKeys.grid_field_addSelectOption.tr()); await tapButtonWithName(LocaleKeys.grid_field_addSelectOption.tr());
} }
Future<void> 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) { Finder finderForDatabaseLayoutType(DatabaseLayoutPB layout) {

View File

@ -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/log.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.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:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'field_service.dart'; import 'field_service.dart';
@ -8,13 +10,18 @@ part 'field_action_sheet_bloc.freezed.dart';
class FieldActionSheetBloc class FieldActionSheetBloc
extends Bloc<FieldActionSheetEvent, FieldActionSheetState> { extends Bloc<FieldActionSheetEvent, FieldActionSheetState> {
final String fieldId;
final FieldBackendService fieldService; final FieldBackendService fieldService;
final FieldSettingsBackendService fieldSettingsService;
FieldActionSheetBloc({required FieldContext fieldCellContext}) FieldActionSheetBloc({required FieldContext fieldCellContext})
: fieldService = FieldBackendService( : fieldId = fieldCellContext.field.id,
fieldService = FieldBackendService(
viewId: fieldCellContext.viewId, viewId: fieldCellContext.viewId,
fieldId: fieldCellContext.field.id, fieldId: fieldCellContext.field.id,
), ),
fieldSettingsService =
FieldSettingsBackendService(viewId: fieldCellContext.viewId),
super( super(
FieldActionSheetState.initial( FieldActionSheetState.initial(
TypeOptionPB.create()..field_2 = fieldCellContext.field, TypeOptionPB.create()..field_2 = fieldCellContext.field,
@ -31,14 +38,20 @@ class FieldActionSheetBloc
); );
}, },
hideField: (_HideField value) async { hideField: (_HideField value) async {
final result = await fieldService.updateField(visibility: false); final result = await fieldSettingsService.updateFieldSettings(
fieldId: fieldId,
fieldVisibility: FieldVisibility.AlwaysHidden,
);
result.fold( result.fold(
(l) => null, (l) => null,
(err) => Log.error(err), (err) => Log.error(err),
); );
}, },
showField: (_ShowField value) async { showField: (_ShowField value) async {
final result = await fieldService.updateField(visibility: true); final result = await fieldSettingsService.updateFieldSettings(
fieldId: fieldId,
fieldVisibility: FieldVisibility.AlwaysShown,
);
result.fold( result.fold(
(l) => null, (l) => null,
(err) => Log.error(err), (err) => Log.error(err),

View File

@ -417,7 +417,7 @@ class FieldController {
_fieldNotifier.fieldInfos = newFieldInfos; _fieldNotifier.fieldInfos = newFieldInfos;
} }
List<FieldInfo> updateFields(List<FieldPB> updatedFieldPBs) { Future<List<FieldInfo>> updateFields(List<FieldPB> updatedFieldPBs) async {
if (updatedFieldPBs.isEmpty) { if (updatedFieldPBs.isEmpty) {
return []; return [];
} }
@ -429,7 +429,8 @@ class FieldController {
newFields.indexWhere((field) => field.id == updatedFieldPB.id); newFields.indexWhere((field) => field.id == updatedFieldPB.id);
if (index != -1) { if (index != -1) {
newFields.removeAt(index); newFields.removeAt(index);
final fieldInfo = FieldInfo.initial(updatedFieldPB); final initial = FieldInfo.initial(updatedFieldPB);
final fieldInfo = await attachFieldSettings(initial);
newFields.insert(index, fieldInfo); newFields.insert(index, fieldInfo);
updatedFields.add(fieldInfo); updatedFields.add(fieldInfo);
} }
@ -443,16 +444,16 @@ class FieldController {
// Listen on field's changes // Listen on field's changes
_fieldListener.start( _fieldListener.start(
onFieldsChanged: (result) { onFieldsChanged: (result) async {
result.fold( result.fold(
(changeset) { (changeset) async {
if (_isDisposed) { if (_isDisposed) {
return; return;
} }
deleteFields(changeset.deletedFields); deleteFields(changeset.deletedFields);
insertFields(changeset.insertedFields); insertFields(changeset.insertedFields);
final updatedFields = updateFields(changeset.updatedFields); final updatedFields = await updateFields(changeset.updatedFields);
for (final listener in _updatedFieldCallbacks.values) { for (final listener in _updatedFieldCallbacks.values) {
listener(updatedFields); listener(updatedFields);
} }
@ -548,7 +549,7 @@ class FieldController {
_fieldNotifier.fieldInfos = _fieldNotifier.fieldInfos =
newFields.map((field) => FieldInfo.initial(field)).toList(); newFields.map((field) => FieldInfo.initial(field)).toList();
Future.wait([ await Future.wait([
_loadFilters(), _loadFilters(),
_loadSorts(), _loadSorts(),
_loadAllFieldSettings(), _loadAllFieldSettings(),

View File

@ -30,7 +30,6 @@ class FieldBackendService {
Future<Either<Unit, FlowyError>> updateField({ Future<Either<Unit, FlowyError>> updateField({
String? name, String? name,
bool? frozen, bool? frozen,
bool? visibility,
double? width, double? width,
}) { }) {
final payload = FieldChangesetPB.create() final payload = FieldChangesetPB.create()
@ -45,10 +44,6 @@ class FieldBackendService {
payload.frozen = frozen; payload.frozen = frozen;
} }
if (visibility != null) {
payload.visibility = visibility;
}
if (width != null) { if (width != null) {
payload.width = width.toInt(); payload.width = width.toInt();
} }

View File

@ -246,7 +246,8 @@ class RowCache {
// ignore: prefer_collection_literals // ignore: prefer_collection_literals
final cellContextMap = CellContextByFieldId(); final cellContextMap = CellContextByFieldId();
for (final fieldInfo in _fieldDelegate.fieldInfos) { for (final fieldInfo in _fieldDelegate.fieldInfos) {
if (fieldInfo.field.visibility) { if (fieldInfo.visibility != null &&
fieldInfo.visibility != FieldVisibility.AlwaysHidden) {
cellContextMap[fieldInfo.id] = DatabaseCellContext( cellContextMap[fieldInfo.id] = DatabaseCellContext(
rowMeta: rowMeta, rowMeta: rowMeta,
viewId: viewId, viewId: viewId,

View File

@ -1,6 +1,8 @@
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; 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/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/log.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async'; import 'dart:async';
@ -28,13 +30,13 @@ class DatabasePropertyBloc
_startListening(); _startListening();
}, },
setFieldVisibility: (_SetFieldVisibility value) async { setFieldVisibility: (_SetFieldVisibility value) async {
final fieldBackendSvc = FieldBackendService( final fieldSettingsSvc = FieldSettingsBackendService(
viewId: viewId, viewId: viewId,
fieldId: value.fieldId,
); );
final result = await fieldBackendSvc.updateField( final result = await fieldSettingsSvc.updateFieldSettings(
visibility: value.visibility, fieldId: value.fieldId,
fieldVisibility: value.visibility,
); );
result.fold((l) => null, (err) => Log.error(err)); result.fold((l) => null, (err) => Log.error(err));
@ -84,7 +86,7 @@ class DatabasePropertyEvent with _$DatabasePropertyEvent {
const factory DatabasePropertyEvent.initial() = _Initial; const factory DatabasePropertyEvent.initial() = _Initial;
const factory DatabasePropertyEvent.setFieldVisibility( const factory DatabasePropertyEvent.setFieldVisibility(
String fieldId, String fieldId,
bool visibility, FieldVisibility visibility,
) = _SetFieldVisibility; ) = _SetFieldVisibility;
const factory DatabasePropertyEvent.didReceiveFieldUpdate( const factory DatabasePropertyEvent.didReceiveFieldUpdate(
List<FieldInfo> fields, List<FieldInfo> fields,

View File

@ -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/plugins/database_view/application/field/field_info.dart';
import 'package:appflowy_backend/log.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_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async'; import 'dart:async';
@ -16,18 +17,25 @@ class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
GridHeaderBloc({ GridHeaderBloc({
required this.viewId, required this.viewId,
required this.fieldController, required this.fieldController,
}) : super(GridHeaderState.initial(fieldController.fieldInfos)) { }) : super(GridHeaderState.initial()) {
on<GridHeaderEvent>( on<GridHeaderEvent>(
(event, emit) async { (event, emit) async {
await event.map( await event.map(
initial: (_InitialHeader value) async { initial: (_InitialHeader value) async {
_startListening(); _startListening();
add(
GridHeaderEvent.didReceiveFieldUpdate(fieldController.fieldInfos),
);
}, },
didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) { didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
emit( emit(
state.copyWith( state.copyWith(
fields: value.fields fields: value.fields
.where((element) => element.field.visibility) .where(
(element) =>
element.visibility != null &&
element.visibility != FieldVisibility.AlwaysHidden,
)
.toList(), .toList(),
), ),
); );
@ -83,9 +91,5 @@ class GridHeaderState with _$GridHeaderState {
const factory GridHeaderState({required List<FieldInfo> fields}) = const factory GridHeaderState({required List<FieldInfo> fields}) =
_GridHeaderState; _GridHeaderState;
factory GridHeaderState.initial(List<FieldInfo> fields) { factory GridHeaderState.initial() => const GridHeaderState(fields: []);
// final List<FieldPB> newFields = List.from(fields);
// newFields.retainWhere((field) => field.visibility);
return GridHeaderState(fields: fields);
}
} }

View File

@ -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/plugins/database_view/application/row/row_service.dart';
import 'package:appflowy_backend/log.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:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async'; import 'dart:async';
@ -10,18 +12,18 @@ part 'row_detail_bloc.freezed.dart';
class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> { class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
final RowBackendService rowService; final RowBackendService rowService;
final RowController dataController; final RowController rowController;
RowDetailBloc({ RowDetailBloc({
required this.dataController, required this.rowController,
}) : rowService = RowBackendService(viewId: dataController.viewId), }) : rowService = RowBackendService(viewId: rowController.viewId),
super(RowDetailState.initial()) { super(RowDetailState.initial()) {
on<RowDetailEvent>( on<RowDetailEvent>(
(event, emit) async { (event, emit) async {
await event.when( await event.when(
initial: () async { initial: () async {
await _startListening(); await _startListening();
final cells = dataController.loadData(); final cells = rowController.loadData();
if (!isClosed) { if (!isClosed) {
add(RowDetailEvent.didReceiveCellDatas(cells.values.toList())); add(RowDetailEvent.didReceiveCellDatas(cells.values.toList()));
} }
@ -32,9 +34,24 @@ class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
deleteField: (fieldId) { deleteField: (fieldId) {
_fieldBackendService(fieldId).deleteField(); _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 { hideField: (fieldId) async {
final result = await _fieldBackendService(fieldId).updateField( final result =
visibility: false, await FieldSettingsBackendService(viewId: rowController.viewId)
.updateFieldSettings(
fieldId: fieldId,
fieldVisibility: FieldVisibility.AlwaysHidden,
); );
result.fold( result.fold(
(l) {}, (l) {},
@ -57,12 +74,12 @@ class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
@override @override
Future<void> close() async { Future<void> close() async {
dataController.dispose(); rowController.dispose();
return super.close(); return super.close();
} }
Future<void> _startListening() async { Future<void> _startListening() async {
dataController.addListener( rowController.addListener(
onRowChanged: (cells, reason) { onRowChanged: (cells, reason) {
if (!isClosed) { if (!isClosed) {
add(RowDetailEvent.didReceiveCellDatas(cells.values.toList())); add(RowDetailEvent.didReceiveCellDatas(cells.values.toList()));
@ -73,7 +90,7 @@ class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
FieldBackendService _fieldBackendService(String fieldId) { FieldBackendService _fieldBackendService(String fieldId) {
return FieldBackendService( return FieldBackendService(
viewId: dataController.viewId, viewId: rowController.viewId,
fieldId: fieldId, fieldId: fieldId,
); );
} }
@ -83,6 +100,7 @@ class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
class RowDetailEvent with _$RowDetailEvent { class RowDetailEvent with _$RowDetailEvent {
const factory RowDetailEvent.initial() = _Initial; const factory RowDetailEvent.initial() = _Initial;
const factory RowDetailEvent.deleteField(String fieldId) = _DeleteField; const factory RowDetailEvent.deleteField(String fieldId) = _DeleteField;
const factory RowDetailEvent.showField(String fieldId) = _ShowField;
const factory RowDetailEvent.hideField(String fieldId) = _HideField; const factory RowDetailEvent.hideField(String fieldId) = _HideField;
const factory RowDetailEvent.deleteRow(String rowId) = _DeleteRow; const factory RowDetailEvent.deleteRow(String rowId) = _DeleteRow;
const factory RowDetailEvent.duplicateRow(String rowId, String? groupId) = const factory RowDetailEvent.duplicateRow(String rowId, String? groupId) =

View File

@ -164,11 +164,6 @@ class FieldCellButton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { 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( return FlowyButton(
hoverColor: AFThemeExtension.of(context).greyHover, hoverColor: AFThemeExtension.of(context).greyHover,
onTap: onTap, onTap: onTap,
@ -177,7 +172,7 @@ class FieldCellButton extends StatelessWidget {
), ),
radius: radius, radius: radius,
text: FlowyText.medium( text: FlowyText.medium(
text, field.name,
maxLines: maxLines, maxLines: maxLines,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),

View File

@ -102,7 +102,7 @@ class _FieldEditorState extends State<FieldEditor> {
builder: (context, state) { builder: (context, state) {
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0), padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: _HideFieldButton( child: HideFieldButton(
popoverMutex: popoverMutex, popoverMutex: popoverMutex,
onHidden: () { onHidden: () {
state.field.fold( state.field.fold(
@ -236,11 +236,12 @@ class _DeleteFieldButton extends StatelessWidget {
} }
} }
class _HideFieldButton extends StatelessWidget { @visibleForTesting
class HideFieldButton extends StatelessWidget {
final PopoverMutex popoverMutex; final PopoverMutex popoverMutex;
final VoidCallback? onHidden; final VoidCallback? onHidden;
const _HideFieldButton({ const HideFieldButton({
required this.popoverMutex, required this.popoverMutex,
required this.onHidden, required this.onHidden,
Key? key, Key? key,

View File

@ -42,12 +42,10 @@ class _GridHeaderSliverAdaptorState extends State<GridHeaderSliverAdaptor> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) { create: (context) {
final bloc = getIt<GridHeaderBloc>( return getIt<GridHeaderBloc>(
param1: widget.viewId, param1: widget.viewId,
param2: widget.fieldController, param2: widget.fieldController,
); )..add(const GridHeaderEvent.initial());
bloc.add(const GridHeaderEvent.initial());
return bloc;
}, },
child: BlocBuilder<GridHeaderBloc, GridHeaderState>( child: BlocBuilder<GridHeaderBloc, GridHeaderState>(
buildWhen: (previous, current) => buildWhen: (previous, current) =>
@ -97,7 +95,6 @@ class _GridHeaderState extends State<_GridHeader> {
buildWhen: (previous, current) => previous.fields != current.fields, buildWhen: (previous, current) => previous.fields != current.fields,
builder: (context, state) { builder: (context, state) {
final cells = state.fields final cells = state.fields
.where((fieldInfo) => fieldInfo.field.visibility)
.map( .map(
(field) => FieldContext( (field) => FieldContext(
viewId: widget.viewId, viewId: widget.viewId,

View File

@ -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/application/setting/property_bloc.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_extension.dart';
import 'package:appflowy/startup/startup.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:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/theme_extension.dart';
@ -44,7 +45,7 @@ class _DatabasePropertyListState extends State<DatabasePropertyList> {
child: BlocBuilder<DatabasePropertyBloc, DatabasePropertyState>( child: BlocBuilder<DatabasePropertyBloc, DatabasePropertyState>(
builder: (context, state) { builder: (context, state) {
final cells = state.fieldContexts.map((field) { final cells = state.fieldContexts.map((field) {
return _GridPropertyCell( return GridPropertyCell(
key: ValueKey(field.id), key: ValueKey(field.id),
viewId: widget.viewId, viewId: widget.viewId,
fieldInfo: field, fieldInfo: field,
@ -75,12 +76,13 @@ class _DatabasePropertyListState extends State<DatabasePropertyList> {
} }
} }
class _GridPropertyCell extends StatefulWidget { @visibleForTesting
class GridPropertyCell extends StatefulWidget {
final FieldInfo fieldInfo; final FieldInfo fieldInfo;
final String viewId; final String viewId;
final PopoverMutex popoverMutex; final PopoverMutex popoverMutex;
const _GridPropertyCell({ const GridPropertyCell({
super.key, super.key,
required this.fieldInfo, required this.fieldInfo,
required this.viewId, required this.viewId,
@ -88,26 +90,22 @@ class _GridPropertyCell extends StatefulWidget {
}); });
@override @override
State<_GridPropertyCell> createState() => _GridPropertyCellState(); State<GridPropertyCell> createState() => _GridPropertyCellState();
} }
class _GridPropertyCellState extends State<_GridPropertyCell> { class _GridPropertyCellState extends State<GridPropertyCell> {
final PopoverController _popoverController = PopoverController(); final PopoverController _popoverController = PopoverController();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final checkmark = FlowySvg( final visiblity = widget.fieldInfo.visibility;
widget.fieldInfo.field.visibility ? FlowySvgs.show_m : FlowySvgs.hide_m, final visibleIcon = FlowySvg(
visiblity != null && visiblity != FieldVisibility.AlwaysHidden
? FlowySvgs.show_m
: FlowySvgs.hide_m,
color: Theme.of(context).iconTheme.color, color: Theme.of(context).iconTheme.color,
); );
return SizedBox(
height: GridSize.popoverItemHeight,
child: _editFieldButton(context, checkmark),
);
}
Widget _editFieldButton(BuildContext context, Widget checkmark) {
return AppFlowyPopover( return AppFlowyPopover(
mutex: widget.popoverMutex, mutex: widget.popoverMutex,
controller: _popoverController, controller: _popoverController,
@ -116,30 +114,40 @@ class _GridPropertyCellState extends State<_GridPropertyCell> {
constraints: BoxConstraints.loose(const Size(240, 400)), constraints: BoxConstraints.loose(const Size(240, 400)),
triggerActions: PopoverTriggerFlags.none, triggerActions: PopoverTriggerFlags.none,
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
child: FlowyButton( child: SizedBox(
hoverColor: AFThemeExtension.of(context).lightGreyHover, height: GridSize.popoverItemHeight,
text: FlowyText.medium( child: FlowyButton(
widget.fieldInfo.name, hoverColor: AFThemeExtension.of(context).lightGreyHover,
color: AFThemeExtension.of(context).textColor, text: FlowyText.medium(
), widget.fieldInfo.name,
leftIcon: FlowySvg( color: AFThemeExtension.of(context).textColor,
widget.fieldInfo.fieldType.icon(), ),
color: Theme.of(context).iconTheme.color, leftIcon: FlowySvg(
), widget.fieldInfo.fieldType.icon(),
rightIcon: FlowyIconButton( color: Theme.of(context).iconTheme.color,
hoverColor: Colors.transparent, ),
onPressed: () { rightIcon: FlowyIconButton(
context.read<DatabasePropertyBloc>().add( hoverColor: Colors.transparent,
DatabasePropertyEvent.setFieldVisibility( onPressed: () {
widget.fieldInfo.id, if (widget.fieldInfo.fieldSettings == null) {
!widget.fieldInfo.field.visibility, return;
), }
);
}, final newVisiblity = _newFieldVisibility(
icon: checkmark.padding(all: 6.0), widget.fieldInfo.fieldSettings!.visibility,
), );
onTap: () => _popoverController.show(), context.read<DatabasePropertyBloc>().add(
).padding(horizontal: 6.0), DatabasePropertyEvent.setFieldVisibility(
widget.fieldInfo.id,
newVisiblity,
),
);
},
icon: visibleIcon.padding(all: 6.0),
),
onTap: () => _popoverController.show(),
).padding(horizontal: 6.0),
),
popupBuilder: (BuildContext context) { popupBuilder: (BuildContext context) {
return FieldEditor( return FieldEditor(
viewId: widget.viewId, 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,
};
}
} }

View File

@ -48,7 +48,7 @@ class _RowDetailPageState extends State<RowDetailPage> {
return FlowyDialog( return FlowyDialog(
child: BlocProvider( child: BlocProvider(
create: (context) { create: (context) {
return RowDetailBloc(dataController: widget.rowController) return RowDetailBloc(rowController: widget.rowController)
..add(const RowDetailEvent.initial()); ..add(const RowDetailEvent.initial());
}, },
child: ListView( child: ListView(

View File

@ -17,7 +17,6 @@ use crate::database::mock_data::{COMPLETED, FACEBOOK, GOOGLE, PAUSED, PLANNED, T
pub fn make_test_board() -> DatabaseData { pub fn make_test_board() -> DatabaseData {
let mut fields = vec![]; let mut fields = vec![];
let mut rows = vec![]; let mut rows = vec![];
// Iterate through the FieldType to create the corresponding Field. // Iterate through the FieldType to create the corresponding Field.
for field_type in FieldType::iter() { for field_type in FieldType::iter() {
match field_type { match field_type {