feat: show hidden fields in row detail page (#3545)

This commit is contained in:
Richard Shiue 2023-10-02 10:52:22 +08:00 committed by GitHub
parent 6198c3907f
commit 0738b5f87d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 456 additions and 207 deletions

View File

@ -146,7 +146,7 @@ void main() {
await tester.openFirstRowDetailPage(); await tester.openFirstRowDetailPage();
// Assert that the first field in the row details page is the select // Assert that the first field in the row details page is the select
// option tyoe // option type
tester.assertFirstFieldInRowDetailByType(FieldType.SingleSelect); tester.assertFirstFieldInRowDetailByType(FieldType.SingleSelect);
// Reorder first field in list // Reorder first field in list
@ -168,6 +168,54 @@ void main() {
tester.assertFirstFieldInRowDetailByType(FieldType.SingleSelect); tester.assertFirstFieldInRowDetailByType(FieldType.SingleSelect);
}); });
testWidgets('hide and show hidden fields', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
// Create a new grid
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Hover first row and then open the row page
await tester.openFirstRowDetailPage();
// Assert that the show hidden fields button isn't visible
tester.assertToggleShowHiddenFieldsVisibility(false);
// Hide the first field in the field list
await tester.tapGridFieldWithNameInRowDetailPage("Type");
await tester.tapHidePropertyButtonInFieldEditor();
// Assert that the field is now hidden
tester.noFieldWithName("Type");
// Assert that the show hidden fields button appears
tester.assertToggleShowHiddenFieldsVisibility(true);
// Click on the show hidden fields button
await tester.toggleShowHiddenFields();
// Assert that the hidden field is shown again and that the show
// hidden fields button is still present
tester.findFieldWithName("Type");
tester.assertToggleShowHiddenFieldsVisibility(true);
// Click hide hidden fields
await tester.toggleShowHiddenFields();
// Assert that the hidden field has vanished
tester.noFieldWithName("Type");
// Click show hidden fields
await tester.toggleShowHiddenFields();
// delete the hidden field
await tester.tapGridFieldWithNameInRowDetailPage("Type");
await tester.tapDeletePropertyInFieldEditor();
// Assert that the that the show hidden fields button is gone
tester.assertToggleShowHiddenFieldsVisibility(false);
});
testWidgets('check document exists in row detail page', (tester) async { testWidgets('check document exists in row detail page', (tester) async {
await tester.initializeAppFlowy(); await tester.initializeAppFlowy();
await tester.tapGoButton(); await tester.tapGoButton();

View File

@ -55,6 +55,7 @@ import 'package:appflowy/plugins/database_view/widgets/row/row_property.dart';
import 'package:appflowy/plugins/database_view/widgets/setting/database_setting.dart'; import 'package:appflowy/plugins/database_view/widgets/setting/database_setting.dart';
import 'package:appflowy/plugins/database_view/widgets/setting/setting_button.dart'; import 'package:appflowy/plugins/database_view/widgets/setting/setting_button.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/emoji_picker/emoji_menu_item.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/emoji_picker/emoji_menu_item.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart'; import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
@ -673,6 +674,31 @@ extension AppFlowyDatabaseTest on WidgetTester {
await pumpAndSettle(); await pumpAndSettle();
} }
void assertToggleShowHiddenFieldsVisibility(bool shown) {
final button = find.byType(ToggleHiddenFieldsVisibilityButton);
if (shown) {
expect(button, findsOneWidget);
} else {
expect(button, findsNothing);
}
}
Future<void> toggleShowHiddenFields() async {
final button = find.byType(ToggleHiddenFieldsVisibilityButton);
await tapButton(button);
}
Future<void> tapDeletePropertyInFieldEditor() async {
final deleteButton = find.byType(DeleteFieldButton);
await tapButton(deleteButton);
final confirmButton = find.descendant(
of: find.byType(NavigatorAlertDialog),
matching: find.byType(PrimaryTextButton),
);
await tapButton(confirmButton);
}
Future<void> scrollGridByOffset(Offset offset) async { Future<void> scrollGridByOffset(Offset offset) async {
await drag(find.byType(GridPage), offset); await drag(find.byType(GridPage), offset);
await pumpAndSettle(); await pumpAndSettle();
@ -746,7 +772,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
} }
Future<void> tapHidePropertyButtonInFieldEditor() async { Future<void> tapHidePropertyButtonInFieldEditor() async {
final button = find.byType(HideFieldButton); final button = find.byType(FieldVisibilityToggleButton);
await tapButton(button); await tapButton(button);
} }
@ -899,7 +925,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
} }
Future<void> assertRowCountInGridPage(int num) async { Future<void> assertRowCountInGridPage(int num) async {
final text = find.text('${rowCountString()} $num',findRichText: true); final text = find.text('${rowCountString()} $num', findRichText: true);
expect(text, findsOneWidget); expect(text, findsOneWidget);
} }

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:collection'; import 'dart:collection';
import 'package:appflowy_backend/protobuf/flowy-database2/checklist_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/checklist_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/timestamp_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/timestamp_entities.pb.dart';
@ -73,4 +74,13 @@ class DatabaseCellContext with _$DatabaseCellContext {
/// Only the primary field can have an emoji. /// Only the primary field can have an emoji.
String? get emoji => fieldInfo.field.isPrimary ? rowMeta.icon : null; String? get emoji => fieldInfo.field.isPrimary ? rowMeta.icon : null;
/// Determines whether a database cell context should be visible.
/// It will be visible when the field is not hidden or when hidden fields
/// should be shown.
bool isVisible({bool showHiddenFields = false}) {
return fieldInfo.visibility != null &&
(showHiddenFields ||
fieldInfo.visibility != FieldVisibility.AlwaysHidden);
}
} }

View File

@ -15,16 +15,16 @@ class FieldActionSheetBloc
final FieldSettingsBackendService fieldSettingsService; final FieldSettingsBackendService fieldSettingsService;
FieldActionSheetBloc({required FieldContext fieldCellContext}) FieldActionSheetBloc({required FieldContext fieldCellContext})
: fieldId = fieldCellContext.field.id, : fieldId = fieldCellContext.fieldInfo.id,
fieldService = FieldBackendService( fieldService = FieldBackendService(
viewId: fieldCellContext.viewId, viewId: fieldCellContext.viewId,
fieldId: fieldCellContext.field.id, fieldId: fieldCellContext.fieldInfo.id,
), ),
fieldSettingsService = fieldSettingsService =
FieldSettingsBackendService(viewId: fieldCellContext.viewId), FieldSettingsBackendService(viewId: fieldCellContext.viewId),
super( super(
FieldActionSheetState.initial( FieldActionSheetState.initial(
TypeOptionPB.create()..field_2 = fieldCellContext.field, TypeOptionPB.create()..field_2 = fieldCellContext.fieldInfo.field,
), ),
) { ) {
on<FieldActionSheetEvent>( on<FieldActionSheetEvent>(

View File

@ -15,13 +15,14 @@ class FieldCellBloc extends Bloc<FieldCellEvent, FieldCellState> {
final FieldBackendService _fieldBackendSvc; final FieldBackendService _fieldBackendSvc;
FieldCellBloc({ FieldCellBloc({
required FieldContext cellContext, required FieldContext fieldContext,
}) : _fieldListener = SingleFieldListener(fieldId: cellContext.field.id), }) : _fieldListener =
SingleFieldListener(fieldId: fieldContext.fieldInfo.id),
_fieldBackendSvc = FieldBackendService( _fieldBackendSvc = FieldBackendService(
viewId: cellContext.viewId, viewId: fieldContext.viewId,
fieldId: cellContext.field.id, fieldId: fieldContext.fieldInfo.id,
), ),
super(FieldCellState.initial(cellContext)) { super(FieldCellState.initial(fieldContext)) {
on<FieldCellEvent>( on<FieldCellEvent>(
(event, emit) async { (event, emit) async {
event.when( event.when(
@ -29,7 +30,7 @@ class FieldCellBloc extends Bloc<FieldCellEvent, FieldCellState> {
_startListening(); _startListening();
}, },
didReceiveFieldUpdate: (field) { didReceiveFieldUpdate: (field) {
emit(state.copyWith(field: cellContext.field)); emit(state.copyWith(field: fieldContext.fieldInfo.field));
}, },
onResizeStart: () { onResizeStart: () {
emit(state.copyWith(resizeStart: state.width)); emit(state.copyWith(resizeStart: state.width));
@ -88,8 +89,8 @@ class FieldCellState with _$FieldCellState {
factory FieldCellState.initial(FieldContext cellContext) => FieldCellState( factory FieldCellState.initial(FieldContext cellContext) => FieldCellState(
viewId: cellContext.viewId, viewId: cellContext.viewId,
field: cellContext.field, field: cellContext.fieldInfo.field,
width: cellContext.field.width.toDouble(), width: cellContext.fieldInfo.field.width.toDouble(),
resizeStart: 0, resizeStart: 0,
); );
} }

View File

@ -367,28 +367,13 @@ class FieldController {
/// Listen for field changes in the backend. /// Listen for field changes in the backend.
void _listenOnFieldChanges() { void _listenOnFieldChanges() {
void deleteFields(List<FieldIdPB> deletedFields) {
if (deletedFields.isEmpty) {
return;
}
final List<FieldInfo> newFields = fieldInfos;
final Map<String, FieldIdPB> deletedFieldMap = {
for (var fieldOrder in deletedFields) fieldOrder.fieldId: fieldOrder
};
newFields.retainWhere((field) => (deletedFieldMap[field.id] == null));
_fieldNotifier.fieldInfos = newFields;
}
Future<FieldInfo> attachFieldSettings(FieldInfo fieldInfo) async { Future<FieldInfo> attachFieldSettings(FieldInfo fieldInfo) async {
return _fieldSettingsBackendSvc return _fieldSettingsBackendSvc
.getFieldSettings(fieldInfo.id) .getFieldSettings(fieldInfo.id)
.then((result) { .then((result) {
final fieldSettings = result.fold( final fieldSettings = result.fold(
(fieldSettings) => fieldSettings, (fieldSettings) => fieldSettings,
(err) { (err) => null,
return null;
},
); );
if (fieldSettings == null) { if (fieldSettings == null) {
return fieldInfo; return fieldInfo;
@ -400,9 +385,25 @@ class FieldController {
}); });
} }
Future<void> insertFields(List<IndexFieldPB> insertedFields) async { List<FieldInfo> deleteFields(List<FieldIdPB> deletedFields) {
if (deletedFields.isEmpty) {
return fieldInfos;
}
final List<FieldInfo> newFields = fieldInfos;
final Map<String, FieldIdPB> deletedFieldMap = {
for (final fieldOrder in deletedFields) fieldOrder.fieldId: fieldOrder
};
newFields.retainWhere((field) => (deletedFieldMap[field.id] == null));
return newFields;
}
Future<List<FieldInfo>> insertFields(
List<IndexFieldPB> insertedFields,
List<FieldInfo> fieldInfos,
) async {
if (insertedFields.isEmpty) { if (insertedFields.isEmpty) {
return; return fieldInfos;
} }
final List<FieldInfo> newFieldInfos = fieldInfos; final List<FieldInfo> newFieldInfos = fieldInfos;
for (final indexField in insertedFields) { for (final indexField in insertedFields) {
@ -414,32 +415,32 @@ class FieldController {
newFieldInfos.add(fieldInfo); newFieldInfos.add(fieldInfo);
} }
} }
_fieldNotifier.fieldInfos = newFieldInfos; return newFieldInfos;
} }
Future<List<FieldInfo>> updateFields(List<FieldPB> updatedFieldPBs) async { Future<(List<FieldInfo>, List<FieldInfo>)> updateFields(
List<FieldPB> updatedFieldPBs,
List<FieldInfo> fieldInfos,
) async {
if (updatedFieldPBs.isEmpty) { if (updatedFieldPBs.isEmpty) {
return []; return (<FieldInfo>[], fieldInfos);
} }
final List<FieldInfo> newFields = fieldInfos; final List<FieldInfo> newFieldInfo = fieldInfos;
final List<FieldInfo> updatedFields = []; final List<FieldInfo> updatedFields = [];
for (final updatedFieldPB in updatedFieldPBs) { for (final updatedFieldPB in updatedFieldPBs) {
final index = final index =
newFields.indexWhere((field) => field.id == updatedFieldPB.id); newFieldInfo.indexWhere((field) => field.id == updatedFieldPB.id);
if (index != -1) { if (index != -1) {
newFields.removeAt(index); newFieldInfo.removeAt(index);
final initial = FieldInfo.initial(updatedFieldPB); final initial = FieldInfo.initial(updatedFieldPB);
final fieldInfo = await attachFieldSettings(initial); final fieldInfo = await attachFieldSettings(initial);
newFields.insert(index, fieldInfo); newFieldInfo.insert(index, fieldInfo);
updatedFields.add(fieldInfo); updatedFields.add(fieldInfo);
} }
} }
if (updatedFields.isNotEmpty) { return (updatedFields, newFieldInfo);
_fieldNotifier.fieldInfos = newFields;
}
return updatedFields;
} }
// Listen on field's changes // Listen on field's changes
@ -450,10 +451,14 @@ class FieldController {
if (_isDisposed) { if (_isDisposed) {
return; return;
} }
deleteFields(changeset.deletedFields); List<FieldInfo> updatedFields;
insertFields(changeset.insertedFields); List<FieldInfo> fieldInfos = deleteFields(changeset.deletedFields);
fieldInfos =
await insertFields(changeset.insertedFields, fieldInfos);
(updatedFields, fieldInfos) =
await updateFields(changeset.updatedFields, fieldInfos);
final updatedFields = await updateFields(changeset.updatedFields); _fieldNotifier.fieldInfos = fieldInfos;
for (final listener in _updatedFieldCallbacks.values) { for (final listener in _updatedFieldCallbacks.values) {
listener(updatedFields); listener(updatedFields);
} }

View File

@ -1,3 +1,4 @@
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/database_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/database_entities.pb.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart';
@ -108,6 +109,6 @@ class FieldBackendService {
class FieldContext with _$FieldContext { class FieldContext with _$FieldContext {
const factory FieldContext({ const factory FieldContext({
required String viewId, required String viewId,
required FieldPB field, required FieldInfo fieldInfo,
}) = _FieldCellContext; }) = _FieldCellContext;
} }

View File

@ -20,7 +20,14 @@ class FieldSettingsBackendService {
return DatabaseEventGetFieldSettings(payload).send().then((result) { return DatabaseEventGetFieldSettings(payload).send().then((result) {
return result.fold( return result.fold(
(fieldSettings) => left(fieldSettings.items.first), (repeatedFieldSettings) {
final fieldSetting = repeatedFieldSettings.items.first;
if (!fieldSetting.hasVisibility()) {
fieldSetting.visibility = FieldVisibility.AlwaysShown;
}
return left(fieldSetting);
},
(r) => right(r), (r) => right(r),
); );
}); });
@ -31,7 +38,18 @@ class FieldSettingsBackendService {
return DatabaseEventGetAllFieldSettings(payload).send().then((result) { return DatabaseEventGetAllFieldSettings(payload).send().then((result) {
return result.fold( return result.fold(
(fieldSettings) => left(fieldSettings.items), (repeatedFieldSettings) {
final fieldSettings = <FieldSettingsPB>[];
for (final fieldSetting in repeatedFieldSettings.items) {
if (!fieldSetting.hasVisibility()) {
fieldSetting.visibility = FieldVisibility.AlwaysShown;
}
fieldSettings.add(fieldSetting);
}
return left(fieldSettings);
},
(r) => right(r), (r) => right(r),
); );
}); });

View File

@ -246,14 +246,11 @@ 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.visibility != null && cellContextMap[fieldInfo.id] = DatabaseCellContext(
fieldInfo.visibility != FieldVisibility.AlwaysHidden) { rowMeta: rowMeta,
cellContextMap[fieldInfo.id] = DatabaseCellContext( viewId: viewId,
rowMeta: rowMeta, fieldInfo: fieldInfo,
viewId: viewId, );
fieldInfo: fieldInfo,
);
}
} }
return cellContextMap; return cellContextMap;
} }

View File

@ -23,12 +23,14 @@ class CalendarEventEditorBloc
await event.when( await event.when(
initial: () { initial: () {
_startListening(); _startListening();
final cells = rowController.loadData(); final cells = rowController
.loadData()
.values
.where((cellContext) => cellContext.isVisible())
.toList();
if (!isClosed) { if (!isClosed) {
add( add(
CalendarEventEditorEvent.didReceiveCellDatas( CalendarEventEditorEvent.didReceiveCellDatas(cells),
cells.values.toList(),
),
); );
} }
}, },
@ -47,8 +49,11 @@ class CalendarEventEditorBloc
rowController.addListener( rowController.addListener(
onRowChanged: (cells, reason) { onRowChanged: (cells, reason) {
if (!isClosed) { if (!isClosed) {
final cellData = cells.values
.where((cellContext) => cellContext.isVisible())
.toList();
add( add(
CalendarEventEditorEvent.didReceiveCellDatas(cells.values.toList()), CalendarEventEditorEvent.didReceiveCellDatas(cellData),
); );
} }
}, },

View File

@ -39,6 +39,8 @@ class RowBloc extends Bloc<RowEvent, RowState> {
_rowBackendSvc.createRowAfterRow(rowId); _rowBackendSvc.createRowAfterRow(rowId);
}, },
didReceiveCells: (cellByFieldId, reason) async { didReceiveCells: (cellByFieldId, reason) async {
cellByFieldId
.removeWhere((_, cellContext) => !cellContext.isVisible());
final cells = cellByFieldId.values final cells = cellByFieldId.values
.map((e) => GridCellEquatable(e.fieldInfo)) .map((e) => GridCellEquatable(e.fieldInfo))
.toList(); .toList();
@ -106,16 +108,18 @@ class RowState with _$RowState {
factory RowState.initial( factory RowState.initial(
CellContextByFieldId cellByFieldId, CellContextByFieldId cellByFieldId,
) => ) {
RowState( cellByFieldId.removeWhere((_, cellContext) => !cellContext.isVisible());
cellByFieldId: cellByFieldId, return RowState(
cells: UnmodifiableListView( cellByFieldId: cellByFieldId,
cellByFieldId.values cells: UnmodifiableListView(
.map((e) => GridCellEquatable(e.fieldInfo)) cellByFieldId.values
.toList(), .map((e) => GridCellEquatable(e.fieldInfo))
), .toList(),
rowSource: const RowSourece.disk(), ),
); rowSource: const RowSourece.disk(),
);
}
} }
class GridCellEquatable extends Equatable { class GridCellEquatable extends Equatable {

View File

@ -14,49 +14,73 @@ class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
RowDetailBloc({ RowDetailBloc({
required this.rowController, required this.rowController,
}) : super(RowDetailState.initial()) { }) : super(RowDetailState.initial(rowController.loadData())) {
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 = rowController.loadData();
if (!isClosed) {
add(RowDetailEvent.didReceiveCellDatas(cells.values.toList()));
}
}, },
didReceiveCellDatas: (cells) { didReceiveCellDatas: (visibleCells, allCells, numHiddenFields) {
emit(state.copyWith(cells: cells)); emit(
state.copyWith(
visibleCells: visibleCells,
allCells: allCells,
numHiddenFields: numHiddenFields,
),
);
}, },
deleteField: (fieldId) { deleteField: (fieldId) {
_fieldBackendService(fieldId).deleteField(); final fieldService = FieldBackendService(
viewId: rowController.viewId,
fieldId: fieldId,
);
fieldService.deleteField();
}, },
showField: (fieldId) async { toggleFieldVisibility: (fieldId) async {
final fieldInfo = state.allCells
.where((cellContext) => cellContext.fieldId == fieldId)
.first
.fieldInfo;
final fieldVisibility =
fieldInfo.visibility == FieldVisibility.AlwaysShown
? FieldVisibility.AlwaysHidden
: FieldVisibility.AlwaysShown;
final result = final result =
await FieldSettingsBackendService(viewId: rowController.viewId) await FieldSettingsBackendService(viewId: rowController.viewId)
.updateFieldSettings( .updateFieldSettings(
fieldId: fieldId, fieldId: fieldId,
fieldVisibility: FieldVisibility.AlwaysShown, fieldVisibility: fieldVisibility,
); );
result.fold( result.fold(
(l) {}, (l) {},
(err) => Log.error(err), (err) => Log.error(err),
); );
}, },
hideField: (fieldId) async { reorderField:
final result = (reorderedFieldId, targetFieldId, fromIndex, toIndex) async {
await FieldSettingsBackendService(viewId: rowController.viewId) await _reorderField(
.updateFieldSettings( reorderedFieldId,
fieldId: fieldId, targetFieldId,
fieldVisibility: FieldVisibility.AlwaysHidden, fromIndex,
); toIndex,
result.fold( emit,
(l) {},
(err) => Log.error(err),
); );
}, },
reorderField: (fieldId, fromIndex, toIndex) async { toggleHiddenFieldVisibility: () {
await _reorderField(fieldId, fromIndex, toIndex, emit); final showHiddenFields = !state.showHiddenFields;
final visibleCells = List<DatabaseCellContext>.from(state.allCells);
visibleCells.retainWhere(
(cellContext) =>
!cellContext.fieldInfo.isPrimary &&
cellContext.isVisible(showHiddenFields: showHiddenFields),
);
emit(
state.copyWith(
showHiddenFields: showHiddenFields,
visibleCells: visibleCells,
),
);
}, },
); );
}, },
@ -71,36 +95,60 @@ class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
Future<void> _startListening() async { Future<void> _startListening() async {
rowController.addListener( rowController.addListener(
onRowChanged: (cells, reason) { onRowChanged: (cellMap, reason) {
if (!isClosed) { if (isClosed) {
add(RowDetailEvent.didReceiveCellDatas(cells.values.toList())); return;
} }
final allCells = cellMap.values.toList();
int numHiddenFields = 0;
final visibleCells = <DatabaseCellContext>[];
for (final cell in allCells) {
final isPrimary = cell.fieldInfo.isPrimary;
if (cell.isVisible(showHiddenFields: state.showHiddenFields) &&
!isPrimary) {
visibleCells.add(cell);
}
if (!cell.isVisible() && !isPrimary) {
numHiddenFields++;
}
}
add(
RowDetailEvent.didReceiveCellDatas(
visibleCells,
allCells,
numHiddenFields,
),
);
}, },
); );
} }
FieldBackendService _fieldBackendService(String fieldId) {
return FieldBackendService(
viewId: rowController.viewId,
fieldId: fieldId,
);
}
Future<void> _reorderField( Future<void> _reorderField(
String fieldId, String reorderedFieldId,
String targetFieldId,
int fromIndex, int fromIndex,
int toIndex, int toIndex,
Emitter<RowDetailState> emit, Emitter<RowDetailState> emit,
) async { ) async {
final cells = List<DatabaseCellContext>.from(state.cells); final cells = List<DatabaseCellContext>.from(state.visibleCells);
cells.insert(toIndex, cells.removeAt(fromIndex)); cells.insert(toIndex, cells.removeAt(fromIndex));
emit(state.copyWith(cells: cells)); emit(state.copyWith(visibleCells: cells));
final fieldService = final fromIndexInAllFields =
FieldBackendService(viewId: rowController.viewId, fieldId: fieldId); state.allCells.indexWhere((cell) => cell.fieldId == reorderedFieldId);
final toIndexInAllFields =
state.allCells.indexWhere((cell) => cell.fieldId == targetFieldId);
final fieldService = FieldBackendService(
viewId: rowController.viewId,
fieldId: reorderedFieldId,
);
final result = await fieldService.moveField( final result = await fieldService.moveField(
fromIndex, fromIndexInAllFields,
toIndex, toIndexInAllFields,
); );
result.fold((l) {}, (err) => Log.error(err)); result.fold((l) {}, (err) => Log.error(err));
} }
@ -110,25 +158,54 @@ 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.toggleFieldVisibility(String fieldId) =
const factory RowDetailEvent.hideField(String fieldId) = _HideField; _ToggleFieldVisibility;
const factory RowDetailEvent.reorderField( const factory RowDetailEvent.reorderField(
String fieldId, String reorderFieldID,
String targetFieldID,
int fromIndex, int fromIndex,
int toIndex, int toIndex,
) = _ReorderField; ) = _ReorderField;
const factory RowDetailEvent.toggleHiddenFieldVisibility() =
_ToggleHiddenFieldVisibility;
const factory RowDetailEvent.didReceiveCellDatas( const factory RowDetailEvent.didReceiveCellDatas(
List<DatabaseCellContext> gridCells, List<DatabaseCellContext> visibleCells,
List<DatabaseCellContext> allCells,
int numHiddenFields,
) = _DidReceiveCellDatas; ) = _DidReceiveCellDatas;
} }
@freezed @freezed
class RowDetailState with _$RowDetailState { class RowDetailState with _$RowDetailState {
const factory RowDetailState({ const factory RowDetailState({
required List<DatabaseCellContext> cells, required List<DatabaseCellContext> visibleCells,
required List<DatabaseCellContext> allCells,
required bool showHiddenFields,
required int numHiddenFields,
}) = _RowDetailState; }) = _RowDetailState;
factory RowDetailState.initial() => RowDetailState( factory RowDetailState.initial(CellContextByFieldId cellByFieldId) {
cells: List.empty(), final allCells = cellByFieldId.values.toList();
); int numHiddenFields = 0;
final visibleCells = <DatabaseCellContext>[];
for (final cell in allCells) {
final isVisible = cell.isVisible();
final isPrimary = cell.fieldInfo.isPrimary;
if (isVisible && !isPrimary) {
visibleCells.add(cell);
}
if (!isVisible && !isPrimary) {
numHiddenFields++;
}
}
return RowDetailState(
visibleCells: visibleCells,
allCells: allCells,
showHiddenFields: false,
numHiddenFields: numHiddenFields,
);
}
} }

View File

@ -38,7 +38,7 @@ class _GridFieldCellState extends State<GridFieldCell> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) { create: (context) {
return FieldCellBloc(cellContext: widget.cellContext); return FieldCellBloc(fieldContext: widget.cellContext);
}, },
child: BlocBuilder<FieldCellBloc, FieldCellState>( child: BlocBuilder<FieldCellBloc, FieldCellState>(
builder: (context, state) { builder: (context, state) {
@ -54,7 +54,7 @@ class _GridFieldCellState extends State<GridFieldCell> {
); );
}, },
child: FieldCellButton( child: FieldCellButton(
field: widget.cellContext.field, field: widget.cellContext.fieldInfo.field,
onTap: () => popoverController.show(), onTap: () => popoverController.show(),
), ),
); );

View File

@ -34,14 +34,14 @@ class _GridFieldCellActionSheetState extends State<GridFieldCellActionSheet> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (_showFieldEditor) { if (_showFieldEditor) {
final field = widget.cellContext.field;
return SizedBox( return SizedBox(
width: 400, width: 400,
child: FieldEditor( child: FieldEditor(
viewId: widget.cellContext.viewId, viewId: widget.cellContext.viewId,
fieldInfo: widget.cellContext.fieldInfo,
typeOptionLoader: FieldTypeOptionLoader( typeOptionLoader: FieldTypeOptionLoader(
viewId: widget.cellContext.viewId, viewId: widget.cellContext.viewId,
field: field, field: widget.cellContext.fieldInfo.field,
), ),
), ),
); );
@ -96,8 +96,8 @@ class _EditFieldButton extends StatelessWidget {
} }
class _FieldOperationList extends StatelessWidget { class _FieldOperationList extends StatelessWidget {
final FieldContext fieldInfo; final FieldContext fieldContext;
const _FieldOperationList(this.fieldInfo, {Key? key}) : super(key: key); const _FieldOperationList(this.fieldContext, {Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -128,7 +128,7 @@ class _FieldOperationList extends StatelessWidget {
bool enable = true; bool enable = true;
// If the field is primary, delete and duplicate are disabled. // If the field is primary, delete and duplicate are disabled.
if (fieldInfo.field.isPrimary) { if (fieldContext.fieldInfo.isPrimary) {
switch (action) { switch (action) {
case FieldAction.hide: case FieldAction.hide:
break; break;
@ -145,7 +145,7 @@ class _FieldOperationList extends StatelessWidget {
child: SizedBox( child: SizedBox(
height: GridSize.popoverItemHeight, height: GridSize.popoverItemHeight,
child: FieldActionCell( child: FieldActionCell(
fieldInfo: fieldInfo, fieldInfo: fieldContext,
action: action, action: action,
enable: enable, enable: enable,
), ),
@ -217,7 +217,7 @@ extension _FieldActionExtension on FieldAction {
} }
} }
void run(BuildContext context, FieldContext fieldInfo) { void run(BuildContext context, FieldContext fieldContext) {
switch (this) { switch (this) {
case FieldAction.hide: case FieldAction.hide:
context context
@ -228,8 +228,8 @@ extension _FieldActionExtension on FieldAction {
PopoverContainer.of(context).close(); PopoverContainer.of(context).close();
FieldBackendService( FieldBackendService(
viewId: fieldInfo.viewId, viewId: fieldContext.viewId,
fieldId: fieldInfo.field.id, fieldId: fieldContext.fieldInfo.id,
).duplicateField(); ).duplicateField();
break; break;
@ -240,8 +240,8 @@ extension _FieldActionExtension on FieldAction {
title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(), title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(),
confirm: () { confirm: () {
FieldBackendService( FieldBackendService(
viewId: fieldInfo.viewId, viewId: fieldContext.viewId,
fieldId: fieldInfo.field.id, fieldId: fieldContext.fieldInfo.field.id,
).deleteField(); ).deleteField();
}, },
).show(context); ).show(context);

View File

@ -1,6 +1,8 @@
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/plugins/database_view/application/field/field_editor_bloc.dart'; import 'package:appflowy/plugins/database_view/application/field/field_editor_bloc.dart';
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart'; import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pb.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
@ -19,17 +21,19 @@ class FieldEditor extends StatefulWidget {
final String viewId; final String viewId;
final bool isGroupingField; final bool isGroupingField;
final Function(String)? onDeleted; final Function(String)? onDeleted;
final Function(String)? onHidden; final Function(String)? onToggleVisibility;
final FieldTypeOptionLoader typeOptionLoader; final FieldTypeOptionLoader typeOptionLoader;
final FieldInfo? fieldInfo;
const FieldEditor({ const FieldEditor({
required this.viewId, required this.viewId,
required this.typeOptionLoader, required this.typeOptionLoader,
this.fieldInfo,
this.isGroupingField = false, this.isGroupingField = false,
this.onDeleted, this.onDeleted,
this.onHidden, this.onToggleVisibility,
Key? key, super.key,
}) : super(key: key); });
@override @override
State<StatefulWidget> createState() => _FieldEditorState(); State<StatefulWidget> createState() => _FieldEditorState();
@ -53,14 +57,14 @@ class _FieldEditorState extends State<FieldEditor> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final bool requireSpace = widget.onDeleted != null || final bool requireSpace = widget.onDeleted != null ||
widget.onHidden != null || widget.onToggleVisibility != null ||
!widget.typeOptionLoader.field.isPrimary; !widget.typeOptionLoader.field.isPrimary;
final List<Widget> children = [ final List<Widget> children = [
FieldNameTextField(popoverMutex: popoverMutex), FieldNameTextField(popoverMutex: popoverMutex),
if (requireSpace) const VSpace(4), if (requireSpace) const VSpace(4),
if (widget.onDeleted != null) _addDeleteFieldButton(), if (widget.onDeleted != null) _addDeleteFieldButton(),
if (widget.onHidden != null) _addHideFieldButton(), if (widget.onToggleVisibility != null) _addHideFieldButton(),
if (!widget.typeOptionLoader.field.isPrimary) if (!widget.typeOptionLoader.field.isPrimary)
FieldTypeOptionCell(popoverMutex: popoverMutex), FieldTypeOptionCell(popoverMutex: popoverMutex),
]; ];
@ -88,7 +92,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: _DeleteFieldButton( child: DeleteFieldButton(
popoverMutex: popoverMutex, popoverMutex: popoverMutex,
onDeleted: () { onDeleted: () {
state.field.fold( state.field.fold(
@ -107,12 +111,14 @@ 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: FieldVisibilityToggleButton(
isFieldHidden:
widget.fieldInfo!.visibility == FieldVisibility.AlwaysHidden,
popoverMutex: popoverMutex, popoverMutex: popoverMutex,
onHidden: () { onTap: () {
state.field.fold( state.field.fold(
() => Log.error('Can not hidden the field'), () => Log.error('Can not hidden the field'),
(field) => widget.onHidden?.call(field.id), (field) => widget.onToggleVisibility?.call(field.id),
); );
}, },
), ),
@ -218,15 +224,16 @@ class _FieldNameTextFieldState extends State<FieldNameTextField> {
} }
} }
class _DeleteFieldButton extends StatelessWidget { @visibleForTesting
class DeleteFieldButton extends StatelessWidget {
final PopoverMutex popoverMutex; final PopoverMutex popoverMutex;
final VoidCallback? onDeleted; final VoidCallback? onDeleted;
const _DeleteFieldButton({ const DeleteFieldButton({
required this.popoverMutex, required this.popoverMutex,
required this.onDeleted, required this.onDeleted,
Key? key, super.key,
}) : super(key: key); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -253,15 +260,17 @@ class _DeleteFieldButton extends StatelessWidget {
} }
@visibleForTesting @visibleForTesting
class HideFieldButton extends StatelessWidget { class FieldVisibilityToggleButton extends StatelessWidget {
final bool isFieldHidden;
final PopoverMutex popoverMutex; final PopoverMutex popoverMutex;
final VoidCallback? onHidden; final VoidCallback? onTap;
const HideFieldButton({ const FieldVisibilityToggleButton({
required this.isFieldHidden,
required this.popoverMutex, required this.popoverMutex,
required this.onHidden, required this.onTap,
Key? key, super.key,
}) : super(key: key); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -270,10 +279,13 @@ class HideFieldButton extends StatelessWidget {
builder: (context, state) { builder: (context, state) {
final Widget button = FlowyButton( final Widget button = FlowyButton(
text: FlowyText.medium( text: FlowyText.medium(
LocaleKeys.grid_field_hide.tr(), isFieldHidden
? LocaleKeys.grid_field_show.tr()
: LocaleKeys.grid_field_hide.tr(),
), ),
leftIcon: const FlowySvg(FlowySvgs.hide_s), leftIcon:
onTap: () => onHidden?.call(), FlowySvg(isFieldHidden ? FlowySvgs.show_m : FlowySvgs.hide_m),
onTap: onTap,
onHover: (_) => popoverMutex.close(), onHover: (_) => popoverMutex.close(),
); );
return SizedBox(height: GridSize.popoverItemHeight, child: button); return SizedBox(height: GridSize.popoverItemHeight, child: button);

View File

@ -98,12 +98,12 @@ class _GridHeaderState extends State<_GridHeader> {
.map( .map(
(field) => FieldContext( (field) => FieldContext(
viewId: widget.viewId, viewId: widget.viewId,
field: field.field, fieldInfo: field,
), ),
) )
.map( .map(
(ctx) => GridFieldCell( (ctx) => GridFieldCell(
key: _getKeyById(ctx.field.id), key: _getKeyById(ctx.fieldInfo.id),
cellContext: ctx, cellContext: ctx,
), ),
) )
@ -136,7 +136,7 @@ class _GridHeaderState extends State<_GridHeader> {
int newIndex, int newIndex,
) { ) {
if (cells.length > oldIndex) { if (cells.length > oldIndex) {
final field = cells[oldIndex].cellContext.field; final field = cells[oldIndex].cellContext.fieldInfo.field;
context context
.read<GridHeaderBloc>() .read<GridHeaderBloc>()
.add(GridHeaderEvent.moveField(field, oldIndex, newIndex)); .add(GridHeaderEvent.moveField(field, oldIndex, newIndex));

View File

@ -93,6 +93,8 @@ List<DatabaseCellContext> _makeCells(
CellContextByFieldId originalCellMap, CellContextByFieldId originalCellMap,
) { ) {
final List<DatabaseCellContext> cells = []; final List<DatabaseCellContext> cells = [];
originalCellMap
.removeWhere((fieldId, cellContext) => !cellContext.isVisible());
for (final entry in originalCellMap.entries) { for (final entry in originalCellMap.entries) {
// Filter out the cell if it's fieldId equal to the groupFieldId // Filter out the cell if it's fieldId equal to the groupFieldId
if (groupFieldId != null) { if (groupFieldId != null) {

View File

@ -151,6 +151,7 @@ class _GridPropertyCellState extends State<GridPropertyCell> {
popupBuilder: (BuildContext context) { popupBuilder: (BuildContext context) {
return FieldEditor( return FieldEditor(
viewId: widget.viewId, viewId: widget.viewId,
fieldInfo: widget.fieldInfo,
typeOptionLoader: FieldTypeOptionLoader( typeOptionLoader: FieldTypeOptionLoader(
viewId: widget.viewId, viewId: widget.viewId,
field: widget.fieldInfo.field, field: widget.fieldInfo.field,

View File

@ -37,9 +37,8 @@ class RowPropertyList extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<RowDetailBloc, RowDetailState>( return BlocBuilder<RowDetailBloc, RowDetailState>(
buildWhen: (previous, current) => previous.cells != current.cells,
builder: (context, state) { builder: (context, state) {
final children = state.cells final children = state.visibleCells
.where((element) => !element.fieldInfo.field.isPrimary) .where((element) => !element.fieldInfo.field.isPrimary)
.mapIndexed( .mapIndexed(
(index, cell) => _PropertyCell( (index, cell) => _PropertyCell(
@ -50,18 +49,26 @@ class RowPropertyList extends StatelessWidget {
), ),
) )
.toList(); .toList();
return ReorderableListView( return ReorderableListView(
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
onReorder: (oldIndex, newIndex) { onReorder: (oldIndex, newIndex) {
final reorderedField = children[oldIndex].cellContext.fieldId; // when reorderiing downwards, need to update index
_reorderField( if (oldIndex < newIndex) {
context, newIndex--;
state.cells, }
reorderedField, final reorderedFieldId = children[oldIndex].cellContext.fieldId;
oldIndex, final targetFieldId = children[newIndex].cellContext.fieldId;
newIndex,
); context.read<RowDetailBloc>().add(
RowDetailEvent.reorderField(
reorderedFieldId,
targetFieldId,
oldIndex,
newIndex,
),
);
}, },
buildDefaultDragHandles: false, buildDefaultDragHandles: false,
proxyDecorator: (child, index, animation) => Material( proxyDecorator: (child, index, animation) => Material(
@ -84,41 +91,22 @@ class RowPropertyList extends StatelessWidget {
), ),
footer: Padding( footer: Padding(
padding: const EdgeInsets.only(left: 20), padding: const EdgeInsets.only(left: 20),
child: CreateRowFieldButton(viewId: viewId), child: Column(
children: [
if (context.read<RowDetailBloc>().state.numHiddenFields != 0)
const Padding(
padding: EdgeInsets.only(bottom: 4.0),
child: ToggleHiddenFieldsVisibilityButton(),
),
CreateRowFieldButton(viewId: viewId),
],
),
), ),
children: children, children: children,
); );
}, },
); );
} }
void _reorderField(
BuildContext context,
List<DatabaseCellContext> cells,
String reorderedFieldId,
int oldIndex,
int newIndex,
) {
// when reorderiing downwards, need to update index
if (oldIndex < newIndex) {
newIndex--;
}
// also update index when the index is after the index of the primary field
// in the original list of DatabaseCellContext's
final primaryFieldIndex =
cells.indexWhere((element) => element.fieldInfo.isPrimary);
if (oldIndex >= primaryFieldIndex) {
oldIndex++;
}
if (newIndex >= primaryFieldIndex) {
newIndex++;
}
context.read<RowDetailBloc>().add(
RowDetailEvent.reorderField(reorderedFieldId, oldIndex, newIndex),
);
}
} }
class _PropertyCell extends StatefulWidget { class _PropertyCell extends StatefulWidget {
@ -208,14 +196,17 @@ class _PropertyCellState extends State<_PropertyCell> {
Widget buildFieldEditor() { Widget buildFieldEditor() {
return FieldEditor( return FieldEditor(
viewId: widget.cellContext.viewId, viewId: widget.cellContext.viewId,
fieldInfo: widget.cellContext.fieldInfo,
isGroupingField: widget.cellContext.fieldInfo.isGroupField, isGroupingField: widget.cellContext.fieldInfo.isGroupField,
typeOptionLoader: FieldTypeOptionLoader( typeOptionLoader: FieldTypeOptionLoader(
viewId: widget.cellContext.viewId, viewId: widget.cellContext.viewId,
field: widget.cellContext.fieldInfo.field, field: widget.cellContext.fieldInfo.field,
), ),
onHidden: (fieldId) { onToggleVisibility: (fieldId) {
_popoverController.close(); _popoverController.close();
context.read<RowDetailBloc>().add(RowDetailEvent.hideField(fieldId)); context
.read<RowDetailBloc>()
.add(RowDetailEvent.toggleFieldVisibility(fieldId));
}, },
onDeleted: (fieldId) { onDeleted: (fieldId) {
_popoverController.close(); _popoverController.close();
@ -288,6 +279,43 @@ GridCellStyle? _customCellStyle(FieldType fieldType) {
throw UnimplementedError; throw UnimplementedError;
} }
class ToggleHiddenFieldsVisibilityButton extends StatelessWidget {
const ToggleHiddenFieldsVisibilityButton({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<RowDetailBloc, RowDetailState>(
builder: (context, state) {
final text = switch (state.showHiddenFields) {
false => LocaleKeys.grid_rowPage_showHiddenFields
.plural(state.numHiddenFields),
true => LocaleKeys.grid_rowPage_hideHiddenFields
.plural(state.numHiddenFields),
};
return SizedBox(
height: 30,
child: FlowyButton(
text: FlowyText.medium(text, color: Theme.of(context).hintColor),
hoverColor: AFThemeExtension.of(context).lightGreyHover,
leftIcon: RotatedBox(
quarterTurns: state.showHiddenFields ? 1 : 3,
child: FlowySvg(
FlowySvgs.arrow_left_s,
color: Theme.of(context).hintColor,
),
),
margin: const EdgeInsets.symmetric(horizontal: 4, vertical: 6),
onTap: () => context.read<RowDetailBloc>().add(
const RowDetailEvent.toggleHiddenFieldVisibility(),
),
),
);
},
);
}
}
class CreateRowFieldButton extends StatefulWidget { class CreateRowFieldButton extends StatefulWidget {
final String viewId; final String viewId;

View File

@ -140,8 +140,8 @@ class BoardTestContext {
} }
FieldContext singleSelectFieldCellContext() { FieldContext singleSelectFieldCellContext() {
final field = singleSelectFieldContext().field; final fieldInfo = singleSelectFieldContext();
return FieldContext(viewId: gridView.id, field: field); return FieldContext(viewId: gridView.id, fieldInfo: fieldInfo);
} }
FieldInfo textFieldContext() { FieldInfo textFieldContext() {

View File

@ -23,8 +23,8 @@ void main() {
blocTest( blocTest(
'update field width', 'update field width',
build: () => FieldCellBloc( build: () => FieldCellBloc(
cellContext: FieldContext( fieldContext: FieldContext(
field: context.fieldContexts[0].field, fieldInfo: context.fieldContexts[0],
viewId: context.gridView.id, viewId: context.gridView.id,
), ),
)..add(const FieldCellEvent.initial()), )..add(const FieldCellEvent.initial()),
@ -42,8 +42,8 @@ void main() {
blocTest( blocTest(
'field width should not be lesser than 50px', 'field width should not be lesser than 50px',
build: () => FieldCellBloc( build: () => FieldCellBloc(
cellContext: FieldContext( fieldContext: FieldContext(
field: context.fieldContexts[0].field, fieldInfo: context.fieldContexts[0],
viewId: context.gridView.id, viewId: context.gridView.id,
), ),
)..add(const FieldCellEvent.initial()), )..add(const FieldCellEvent.initial()),

View File

@ -89,8 +89,8 @@ class GridTestContext {
} }
FieldContext singleSelectFieldCellContext() { FieldContext singleSelectFieldCellContext() {
final field = singleSelectFieldContext().field; final fieldInfo = singleSelectFieldContext();
return FieldContext(viewId: gridView.id, field: field); return FieldContext(viewId: gridView.id, fieldInfo: fieldInfo);
} }
FieldInfo textFieldContext() { FieldInfo textFieldContext() {

View File

@ -411,6 +411,7 @@
}, },
"field": { "field": {
"hide": "Hide", "hide": "Hide",
"show": "Show",
"insertLeft": "Insert Left", "insertLeft": "Insert Left",
"insertRight": "Insert Right", "insertRight": "Insert Right",
"duplicate": "Duplicate", "duplicate": "Duplicate",
@ -447,6 +448,19 @@
"deleteFieldPromptMessage": "Are you sure? This property will be deleted", "deleteFieldPromptMessage": "Are you sure? This property will be deleted",
"newColumn": "New Column" "newColumn": "New Column"
}, },
"rowPage": {
"newField": "Add a new field",
"showHiddenFields": {
"one": "Show {} hidden field",
"many": "Show {} hidden fields",
"other": "Show {} hidden fields"
},
"hideHiddenFields": {
"one": "Hide {} hidden field",
"many": "Hide {} hidden fields",
"other": "Hide {} hidden fields"
}
},
"sort": { "sort": {
"ascending": "Ascending", "ascending": "Ascending",
"descending": "Descending", "descending": "Descending",