瀏覽代碼

chore: grid property list

appflowy 3 年之前
父節點
當前提交
6a94594a35
共有 25 個文件被更改,包括 600 次插入382 次删除
  1. 8 4
      frontend/app_flowy/lib/startup/deps_resolver.dart
  2. 15 15
      frontend/app_flowy/lib/workspace/application/grid/field/action_sheet_bloc.dart
  3. 1 1
      frontend/app_flowy/lib/workspace/application/grid/field/field_editor_bloc.dart
  4. 3 3
      frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart
  5. 3 3
      frontend/app_flowy/lib/workspace/application/grid/field/field_switch_bloc.dart
  6. 5 1
      frontend/app_flowy/lib/workspace/application/grid/field/grid_header_bloc.dart
  7. 4 3
      frontend/app_flowy/lib/workspace/application/grid/prelude.dart
  8. 51 0
      frontend/app_flowy/lib/workspace/application/grid/setting/property_bloc.dart
  9. 21 6
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart
  10. 1 0
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/grid_row.dart
  11. 55 0
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart
  12. 195 0
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_cell_action_sheet.dart
  13. 8 4
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart
  14. 0 99
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_operation_list.dart
  15. 6 5
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_switcher.dart
  16. 44 0
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_type_extension.dart
  17. 1 42
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_type_list.dart
  18. 0 105
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_field_action_sheet.dart
  19. 4 4
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header.dart
  20. 0 45
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header_cell.dart
  21. 12 12
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/date.dart
  22. 109 5
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/toolbar/grid_property.dart
  23. 31 17
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/toolbar/grid_setting.dart
  24. 22 7
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/toolbar/grid_toolbar.dart
  25. 1 1
      frontend/rust-lib/flowy-grid/src/services/grid_editor.rs

+ 8 - 4
frontend/app_flowy/lib/startup/deps_resolver.dart

@@ -165,8 +165,8 @@ void _resolveGridDeps(GetIt getIt) {
     ),
   );
 
-  getIt.registerFactoryParam<GridFieldBloc, GridFieldData, void>(
-    (data, _) => GridFieldBloc(
+  getIt.registerFactoryParam<FieldActionSheetBloc, GridFieldCellContext, void>(
+    (data, _) => FieldActionSheetBloc(
       field: data.field,
       service: FieldService(gridId: data.gridId),
     ),
@@ -214,8 +214,8 @@ void _resolveGridDeps(GetIt getIt) {
     ),
   );
 
-  getIt.registerFactoryParam<FieldSwitchBloc, SwitchFieldContext, void>(
-    (context, _) => FieldSwitchBloc(context),
+  getIt.registerFactoryParam<FieldSwitcherBloc, SwitchFieldContext, void>(
+    (context, _) => FieldSwitcherBloc(context),
   );
 
   getIt.registerFactoryParam<SingleSelectTypeOptionBloc, SingleSelectTypeOption, String>(
@@ -233,4 +233,8 @@ void _resolveGridDeps(GetIt getIt) {
   getIt.registerFactoryParam<NumberTypeOptionBloc, NumberTypeOption, void>(
     (typeOption, _) => NumberTypeOptionBloc(typeOption: typeOption),
   );
+
+  getIt.registerFactoryParam<GridPropertyBloc, String, List<Field>>(
+    (gridId, fields) => GridPropertyBloc(gridId: gridId, fields: fields),
+  );
 }

+ 15 - 15
frontend/app_flowy/lib/workspace/application/grid/field/grid_field_bloc.dart → frontend/app_flowy/lib/workspace/application/grid/field/action_sheet_bloc.dart

@@ -5,14 +5,14 @@ import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';
 import 'field_service.dart';
 
-part 'grid_field_bloc.freezed.dart';
+part 'action_sheet_bloc.freezed.dart';
 
-class GridFieldBloc extends Bloc<GridFieldEvent, GridFieldState> {
+class FieldActionSheetBloc extends Bloc<ActionSheetEvent, ActionSheetState> {
   final FieldService service;
 
-  GridFieldBloc({required Field field, required this.service})
-      : super(GridFieldState.initial(EditFieldContext.create()..gridField = field)) {
-    on<GridFieldEvent>(
+  FieldActionSheetBloc({required Field field, required this.service})
+      : super(ActionSheetState.initial(EditFieldContext.create()..gridField = field)) {
+    on<ActionSheetEvent>(
       (event, emit) async {
         await event.map(
           updateFieldName: (_UpdateFieldName value) async {
@@ -56,23 +56,23 @@ class GridFieldBloc extends Bloc<GridFieldEvent, GridFieldState> {
 }
 
 @freezed
-class GridFieldEvent with _$GridFieldEvent {
-  const factory GridFieldEvent.updateFieldName(String name) = _UpdateFieldName;
-  const factory GridFieldEvent.hideField() = _HideField;
-  const factory GridFieldEvent.duplicateField() = _DuplicateField;
-  const factory GridFieldEvent.deleteField() = _DeleteField;
-  const factory GridFieldEvent.saveField() = _SaveField;
+class ActionSheetEvent with _$ActionSheetEvent {
+  const factory ActionSheetEvent.updateFieldName(String name) = _UpdateFieldName;
+  const factory ActionSheetEvent.hideField() = _HideField;
+  const factory ActionSheetEvent.duplicateField() = _DuplicateField;
+  const factory ActionSheetEvent.deleteField() = _DeleteField;
+  const factory ActionSheetEvent.saveField() = _SaveField;
 }
 
 @freezed
-class GridFieldState with _$GridFieldState {
-  const factory GridFieldState({
+class ActionSheetState with _$ActionSheetState {
+  const factory ActionSheetState({
     required EditFieldContext editContext,
     required String errorText,
     required String fieldName,
-  }) = _GridFieldState;
+  }) = _ActionSheetState;
 
-  factory GridFieldState.initial(EditFieldContext editContext) => GridFieldState(
+  factory ActionSheetState.initial(EditFieldContext editContext) => ActionSheetState(
         editContext: editContext,
         errorText: '',
         fieldName: editContext.gridField.name,

+ 1 - 1
frontend/app_flowy/lib/workspace/application/grid/field/edit_field_bloc.dart → frontend/app_flowy/lib/workspace/application/grid/field/field_editor_bloc.dart

@@ -7,7 +7,7 @@ import 'dart:async';
 import 'field_service.dart';
 import 'package:dartz/dartz.dart';
 
-part 'edit_field_bloc.freezed.dart';
+part 'field_editor_bloc.freezed.dart';
 
 class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
   final FieldService service;

+ 3 - 3
frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart

@@ -98,11 +98,11 @@ class FieldService {
   }
 }
 
-class GridFieldData extends Equatable {
+class GridFieldCellContext extends Equatable {
   final String gridId;
   final Field field;
 
-  const GridFieldData({
+  const GridFieldCellContext({
     required this.gridId,
     required this.field,
   });
@@ -132,7 +132,7 @@ class NewFieldContextLoader extends FieldContextLoader {
 }
 
 class FieldContextLoaderAdaptor extends FieldContextLoader {
-  final GridFieldData data;
+  final GridFieldCellContext data;
 
   FieldContextLoaderAdaptor(this.data);
 

+ 3 - 3
frontend/app_flowy/lib/workspace/application/grid/field/switch_field_type_bloc.dart → frontend/app_flowy/lib/workspace/application/grid/field/field_switch_bloc.dart

@@ -7,10 +7,10 @@ import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';
 import 'field_service.dart';
 
-part 'switch_field_type_bloc.freezed.dart';
+part 'field_switch_bloc.freezed.dart';
 
-class FieldSwitchBloc extends Bloc<FieldSwitchEvent, FieldSwitchState> {
-  FieldSwitchBloc(SwitchFieldContext editContext) : super(FieldSwitchState.initial(editContext)) {
+class FieldSwitcherBloc extends Bloc<FieldSwitchEvent, FieldSwitchState> {
+  FieldSwitcherBloc(SwitchFieldContext editContext) : super(FieldSwitchState.initial(editContext)) {
     on<FieldSwitchEvent>(
       (event, emit) async {
         await event.map(

+ 5 - 1
frontend/app_flowy/lib/workspace/application/grid/field/grid_header_bloc.dart

@@ -27,6 +27,7 @@ class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
           createField: (_CreateField value) {},
           insertField: (_InsertField value) {},
           didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
+            value.fields.retainWhere((field) => field.visibility);
             emit(state.copyWith(fields: value.fields));
           },
         );
@@ -64,5 +65,8 @@ class GridHeaderEvent with _$GridHeaderEvent {
 class GridHeaderState with _$GridHeaderState {
   const factory GridHeaderState({required List<Field> fields}) = _GridHeaderState;
 
-  factory GridHeaderState.initial(List<Field> fields) => GridHeaderState(fields: fields);
+  factory GridHeaderState.initial(List<Field> fields) {
+    fields.retainWhere((field) => field.visibility);
+    return GridHeaderState(fields: fields);
+  }
 }

+ 4 - 3
frontend/app_flowy/lib/workspace/application/grid/prelude.dart

@@ -7,9 +7,9 @@ export 'data.dart';
 // Field
 export 'field/field_service.dart';
 export 'field/grid_header_bloc.dart';
-export 'field/grid_field_bloc.dart';
-export 'field/edit_field_bloc.dart';
-export 'field/switch_field_type_bloc.dart';
+export 'field/action_sheet_bloc.dart';
+export 'field/field_editor_bloc.dart';
+export 'field/field_switch_bloc.dart';
 
 // Field Type Option
 export 'field/type_option/date_bloc.dart';
@@ -26,3 +26,4 @@ export 'cell_bloc/cell_service.dart';
 
 // Setting
 export 'setting/setting_bloc.dart';
+export 'setting/property_bloc.dart';

+ 51 - 0
frontend/app_flowy/lib/workspace/application/grid/setting/property_bloc.dart

@@ -0,0 +1,51 @@
+import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
+import 'package:flowy_sdk/log.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+import 'dart:async';
+import 'package:dartz/dartz.dart';
+
+part 'property_bloc.freezed.dart';
+
+class GridPropertyBloc extends Bloc<GridPropertyEvent, GridPropertyState> {
+  final FieldService _service;
+  GridPropertyBloc({required String gridId, required List<Field> fields})
+      : _service = FieldService(gridId: gridId),
+        super(GridPropertyState.initial(gridId, fields)) {
+    on<GridPropertyEvent>(
+      (event, emit) async {
+        await event.map(setFieldVisibility: (_SetFieldVisibility value) async {
+          final result = await _service.updateField(fieldId: value.fieldId, visibility: value.visibility);
+          result.fold(
+            (l) => null,
+            (err) => Log.error(err),
+          );
+        });
+      },
+    );
+  }
+
+  @override
+  Future<void> close() async {
+    return super.close();
+  }
+}
+
+@freezed
+class GridPropertyEvent with _$GridPropertyEvent {
+  const factory GridPropertyEvent.setFieldVisibility(String fieldId, bool visibility) = _SetFieldVisibility;
+}
+
+@freezed
+class GridPropertyState with _$GridPropertyState {
+  const factory GridPropertyState({
+    required String gridId,
+    required List<Field> fields,
+  }) = _GridPropertyState;
+
+  factory GridPropertyState.initial(String gridId, List<Field> fields) => GridPropertyState(
+        gridId: gridId,
+        fields: fields,
+      );
+}

+ 21 - 6
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart

@@ -100,9 +100,9 @@ class _FlowyGridState extends State<FlowyGrid> {
                   physics: StyledScrollPhysics(),
                   controller: _scrollController.verticalController,
                   slivers: [
-                    SliverToBoxAdapter(child: GridToolbar(gridId: gridId)),
-                    _buildHeader(gridId),
-                    _buildRows(context),
+                    _renderToolbar(gridId),
+                    _renderHeader(gridId),
+                    _renderRows(context),
                     const GridFooter(),
                   ],
                 ),
@@ -129,12 +129,27 @@ class _FlowyGridState extends State<FlowyGrid> {
     );
   }
 
-  Widget _buildHeader(String gridId) {
+  Widget _renderToolbar(String gridId) {
+    return BlocBuilder<GridBloc, GridState>(
+      builder: (context, state) {
+        final toolbarContext = GridToolbarContext(
+          gridId: gridId,
+          fields: state.fields,
+        );
+
+        return SliverToBoxAdapter(
+          child: GridToolbar(toolbarContext: toolbarContext),
+        );
+      },
+    );
+  }
+
+  Widget _renderHeader(String gridId) {
     return BlocBuilder<GridBloc, GridState>(
       buildWhen: (previous, current) => previous.fields.length != current.fields.length,
       builder: (context, state) {
         return SliverPersistentHeader(
-          delegate: GridHeaderDelegate(gridId: gridId, fields: state.fields),
+          delegate: GridHeaderDelegate(gridId: gridId, fields: List.from(state.fields)),
           floating: true,
           pinned: true,
         );
@@ -142,7 +157,7 @@ class _FlowyGridState extends State<FlowyGrid> {
     );
   }
 
-  Widget _buildRows(BuildContext context) {
+  Widget _renderRows(BuildContext context) {
     return BlocBuilder<GridBloc, GridState>(
       buildWhen: (previous, current) {
         final rowChanged = previous.rows.length != current.rows.length;

+ 1 - 0
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/grid_row.dart

@@ -124,6 +124,7 @@ class _RowCells extends StatelessWidget {
       buildWhen: (previous, current) => previous.cellDataMap != current.cellDataMap,
       builder: (context, state) {
         final children = state.fields
+            .where((field) => field.visibility)
             .map((field) => CellContainer(
                   width: field.width.toDouble(),
                   child: buildGridCell(

+ 55 - 0
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart

@@ -0,0 +1,55 @@
+import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
+import 'package:flowy_infra/image.dart';
+import 'package:flowy_infra/theme.dart';
+import 'package:flowy_infra_ui/style_widget/button.dart';
+import 'package:flowy_infra_ui/style_widget/text.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'field_type_extension.dart';
+
+import 'field_cell_action_sheet.dart';
+import 'field_editor.dart';
+
+class GridFieldCell extends StatelessWidget {
+  final GridFieldCellContext fieldCellContext;
+  const GridFieldCell(this.fieldCellContext, {Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    final field = fieldCellContext.field;
+
+    final button = FlowyButton(
+      hoverColor: theme.hover,
+      onTap: () => _showActionSheet(context),
+      rightIcon: svgWidget("editor/details", color: theme.iconColor),
+      leftIcon: svgWidget(field.fieldType.iconName(), color: theme.iconColor),
+      text: FlowyText.medium(field.name, fontSize: 12),
+      padding: GridSize.cellContentInsets,
+    );
+
+    final borderSide = BorderSide(color: theme.shader4, width: 0.4);
+    final decoration = BoxDecoration(border: Border(top: borderSide, right: borderSide, bottom: borderSide));
+
+    return Container(
+      width: field.width.toDouble(),
+      decoration: decoration,
+      child: button,
+    );
+  }
+
+  void _showActionSheet(BuildContext context) {
+    GridFieldCellActionSheet(
+      fieldCellContext: fieldCellContext,
+      onEdited: () => _showFieldEditor(context),
+    ).show(context);
+  }
+
+  void _showFieldEditor(BuildContext context) {
+    FieldEditor(
+      gridId: fieldCellContext.gridId,
+      fieldContextLoader: FieldContextLoaderAdaptor(fieldCellContext),
+    ).show(context);
+  }
+}

+ 195 - 0
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_cell_action_sheet.dart

@@ -0,0 +1,195 @@
+import 'package:app_flowy/startup/startup.dart';
+import 'package:app_flowy/workspace/application/grid/prelude.dart';
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
+import 'package:flowy_infra/image.dart';
+import 'package:flowy_infra/theme.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flowy_infra_ui/style_widget/button.dart';
+import 'package:flowy_infra_ui/style_widget/text.dart';
+import 'package:flowy_infra_ui/widget/spacing.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:easy_localization/easy_localization.dart';
+import 'package:app_flowy/generated/locale_keys.g.dart';
+
+class GridFieldCellActionSheet extends StatelessWidget with FlowyOverlayDelegate {
+  final GridFieldCellContext fieldCellContext;
+  final VoidCallback onEdited;
+  const GridFieldCellActionSheet({required this.fieldCellContext, required this.onEdited, Key? key}) : super(key: key);
+
+  void show(BuildContext overlayContext) {
+    FlowyOverlay.of(overlayContext).insertWithAnchor(
+      widget: OverlayContainer(
+        child: this,
+        constraints: BoxConstraints.loose(const Size(240, 200)),
+      ),
+      identifier: identifier(),
+      anchorContext: overlayContext,
+      anchorDirection: AnchorDirection.bottomWithLeftAligned,
+      delegate: this,
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocProvider(
+      create: (context) => getIt<FieldActionSheetBloc>(param1: fieldCellContext),
+      child: SingleChildScrollView(
+        child: Column(
+          children: [
+            _EditFieldButton(
+              onEdited: () {
+                FlowyOverlay.of(context).remove(identifier());
+                onEdited();
+              },
+            ),
+            const VSpace(6),
+            _FieldOperationList(fieldCellContext, () => FlowyOverlay.of(context).remove(identifier())),
+          ],
+        ),
+      ),
+    );
+  }
+
+  String identifier() {
+    return toString();
+  }
+
+  @override
+  bool asBarrier() {
+    return true;
+  }
+}
+
+class _EditFieldButton extends StatelessWidget {
+  final Function() onEdited;
+  const _EditFieldButton({required this.onEdited, Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    return BlocBuilder<FieldActionSheetBloc, ActionSheetState>(
+      builder: (context, state) {
+        return SizedBox(
+          height: GridSize.typeOptionItemHeight,
+          child: FlowyButton(
+            text: FlowyText.medium(LocaleKeys.grid_field_editProperty.tr(), fontSize: 12),
+            hoverColor: theme.hover,
+            onTap: onEdited,
+          ),
+        );
+      },
+    );
+  }
+}
+
+class _FieldOperationList extends StatelessWidget {
+  final GridFieldCellContext fieldData;
+  final VoidCallback onDismissed;
+  const _FieldOperationList(this.fieldData, this.onDismissed, {Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final actions = FieldAction.values
+        .map(
+          (action) => FieldActionCell(
+            fieldId: fieldData.field.id,
+            action: action,
+            onTap: onDismissed,
+          ),
+        )
+        .toList();
+
+    return FieldOperationList(actions: actions);
+  }
+}
+
+class FieldOperationList extends StatelessWidget {
+  final List<FieldActionCell> actions;
+  const FieldOperationList({required this.actions, Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return GridView(
+      // https://api.flutter.dev/flutter/widgets/AnimatedList/shrinkWrap.html
+      shrinkWrap: true,
+      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
+        crossAxisCount: 2,
+        childAspectRatio: 4.0,
+        mainAxisSpacing: 8,
+      ),
+      children: actions,
+    );
+  }
+}
+
+class FieldActionCell extends StatelessWidget {
+  final String fieldId;
+  final VoidCallback onTap;
+  final FieldAction action;
+
+  const FieldActionCell({
+    required this.fieldId,
+    required this.action,
+    required this.onTap,
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    return FlowyButton(
+      text: FlowyText.medium(action.title(), fontSize: 12),
+      hoverColor: theme.hover,
+      onTap: () {
+        action.run(context);
+        onTap();
+      },
+      leftIcon: svgWidget(action.iconName(), color: theme.iconColor),
+    );
+  }
+}
+
+enum FieldAction {
+  hide,
+  duplicate,
+  delete,
+}
+
+extension _FieldActionExtension on FieldAction {
+  String iconName() {
+    switch (this) {
+      case FieldAction.hide:
+        return 'grid/hide';
+      case FieldAction.duplicate:
+        return 'grid/duplicate';
+      case FieldAction.delete:
+        return 'grid/delete';
+    }
+  }
+
+  String title() {
+    switch (this) {
+      case FieldAction.hide:
+        return LocaleKeys.grid_field_hide.tr();
+      case FieldAction.duplicate:
+        return LocaleKeys.grid_field_duplicate.tr();
+      case FieldAction.delete:
+        return LocaleKeys.grid_field_delete.tr();
+    }
+  }
+
+  void run(BuildContext context) {
+    switch (this) {
+      case FieldAction.hide:
+        context.read<FieldActionSheetBloc>().add(const ActionSheetEvent.hideField());
+        break;
+      case FieldAction.duplicate:
+        context.read<FieldActionSheetBloc>().add(const ActionSheetEvent.duplicateField());
+        break;
+      case FieldAction.delete:
+        context.read<FieldActionSheetBloc>().add(const ActionSheetEvent.deleteField());
+        break;
+    }
+  }
+}

+ 8 - 4
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_field_editor.dart → frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart

@@ -1,7 +1,7 @@
 import 'package:app_flowy/startup/startup.dart';
-import 'package:app_flowy/workspace/application/grid/field/edit_field_bloc.dart';
+import 'package:app_flowy/workspace/application/grid/field/field_editor_bloc.dart';
 import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
-import 'package:app_flowy/workspace/application/grid/field/switch_field_type_bloc.dart';
+import 'package:app_flowy/workspace/application/grid/field/field_switch_bloc.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
@@ -22,7 +22,11 @@ class FieldEditor extends FlowyOverlayDelegate {
     _fieldEditorBloc.add(const FieldEditorEvent.initial());
   }
 
-  void show(BuildContext context) {
+  void show(
+    BuildContext context, {
+    AnchorDirection anchorDirection = AnchorDirection.bottomWithLeftAligned,
+  }) {
+    FlowyOverlay.of(context).remove(identifier());
     FlowyOverlay.of(context).insertWithAnchor(
       widget: OverlayContainer(
         child: _FieldEditorWidget(_fieldEditorBloc),
@@ -30,7 +34,7 @@ class FieldEditor extends FlowyOverlayDelegate {
       ),
       identifier: identifier(),
       anchorContext: context,
-      anchorDirection: AnchorDirection.bottomWithLeftAligned,
+      anchorDirection: anchorDirection,
       style: FlowyOverlayStyle(blur: false),
       delegate: this,
     );

+ 0 - 99
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_operation_list.dart

@@ -1,99 +0,0 @@
-import 'package:app_flowy/workspace/application/grid/field/grid_field_bloc.dart';
-import 'package:flowy_infra/image.dart';
-import 'package:flowy_infra/theme.dart';
-import 'package:flowy_infra_ui/style_widget/button.dart';
-import 'package:flowy_infra_ui/style_widget/text.dart';
-import 'package:flutter/material.dart';
-import 'package:easy_localization/easy_localization.dart';
-import 'package:app_flowy/generated/locale_keys.g.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-
-class FieldOperationList extends StatelessWidget {
-  final List<FieldActionCell> actions;
-  const FieldOperationList({required this.actions, Key? key}) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    return GridView(
-      // https://api.flutter.dev/flutter/widgets/AnimatedList/shrinkWrap.html
-      shrinkWrap: true,
-      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
-        crossAxisCount: 2,
-        childAspectRatio: 4.0,
-        mainAxisSpacing: 8,
-      ),
-      children: actions,
-    );
-  }
-}
-
-class FieldActionCell extends StatelessWidget {
-  final String fieldId;
-  final VoidCallback onTap;
-  final FieldAction action;
-
-  const FieldActionCell({
-    required this.fieldId,
-    required this.action,
-    required this.onTap,
-    Key? key,
-  }) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    final theme = context.watch<AppTheme>();
-    return FlowyButton(
-      text: FlowyText.medium(action.title(), fontSize: 12),
-      hoverColor: theme.hover,
-      onTap: () {
-        action.run(context);
-        onTap();
-      },
-      leftIcon: svgWidget(action.iconName(), color: theme.iconColor),
-    );
-  }
-}
-
-enum FieldAction {
-  hide,
-  duplicate,
-  delete,
-}
-
-extension _FieldActionExtension on FieldAction {
-  String iconName() {
-    switch (this) {
-      case FieldAction.hide:
-        return 'grid/hide';
-      case FieldAction.duplicate:
-        return 'grid/duplicate';
-      case FieldAction.delete:
-        return 'grid/delete';
-    }
-  }
-
-  String title() {
-    switch (this) {
-      case FieldAction.hide:
-        return LocaleKeys.grid_field_hide.tr();
-      case FieldAction.duplicate:
-        return LocaleKeys.grid_field_duplicate.tr();
-      case FieldAction.delete:
-        return LocaleKeys.grid_field_delete.tr();
-    }
-  }
-
-  void run(BuildContext context) {
-    switch (this) {
-      case FieldAction.hide:
-        context.read<GridFieldBloc>().add(const GridFieldEvent.hideField());
-        break;
-      case FieldAction.duplicate:
-        context.read<GridFieldBloc>().add(const GridFieldEvent.duplicateField());
-        break;
-      case FieldAction.delete:
-        context.read<GridFieldBloc>().add(const GridFieldEvent.deleteField());
-        break;
-    }
-  }
-}

+ 6 - 5
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_switcher.dart

@@ -15,7 +15,8 @@ import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/workspace/application/grid/prelude.dart';
-import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_list.dart';
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_type_list.dart';
+import 'field_type_extension.dart';
 
 import 'type_option/multi_select.dart';
 import 'type_option/number.dart';
@@ -43,8 +44,8 @@ class _FieldSwitcherState extends State<FieldSwitcher> {
   @override
   Widget build(BuildContext context) {
     return BlocProvider(
-      create: (context) => getIt<FieldSwitchBloc>(param1: widget.switchContext),
-      child: BlocConsumer<FieldSwitchBloc, FieldSwitchState>(
+      create: (context) => getIt<FieldSwitcherBloc>(param1: widget.switchContext),
+      child: BlocConsumer<FieldSwitcherBloc, FieldSwitchState>(
         listener: (context, state) {
           widget.onUpdated(state.field, state.typeOptionData);
         },
@@ -79,7 +80,7 @@ class _FieldSwitcherState extends State<FieldSwitcher> {
         hoverColor: theme.hover,
         onTap: () {
           final list = FieldTypeList(onSelectField: (fieldType) {
-            context.read<FieldSwitchBloc>().add(FieldSwitchEvent.toFieldType(fieldType));
+            context.read<FieldSwitcherBloc>().add(FieldSwitchEvent.toFieldType(fieldType));
           });
           _showOverlay(context, list);
         },
@@ -100,7 +101,7 @@ class _FieldSwitcherState extends State<FieldSwitcher> {
     );
 
     final dataDelegate = TypeOptionDataDelegate(didUpdateTypeOptionData: (data) {
-      context.read<FieldSwitchBloc>().add(FieldSwitchEvent.didUpdateTypeOptionData(data));
+      context.read<FieldSwitcherBloc>().add(FieldSwitchEvent.didUpdateTypeOptionData(data));
     });
 
     final builder = _makeTypeOptionBuild(

+ 44 - 0
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_type_extension.dart

@@ -0,0 +1,44 @@
+
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/meta.pb.dart';
+import 'package:app_flowy/generated/locale_keys.g.dart';
+import 'package:easy_localization/easy_localization.dart';
+
+extension FieldTypeListExtension on FieldType {
+  String iconName() {
+    switch (this) {
+      case FieldType.Checkbox:
+        return "grid/field/checkbox";
+      case FieldType.DateTime:
+        return "grid/field/date";
+      case FieldType.MultiSelect:
+        return "grid/field/multi_select";
+      case FieldType.Number:
+        return "grid/field/number";
+      case FieldType.RichText:
+        return "grid/field/text";
+      case FieldType.SingleSelect:
+        return "grid/field/single_select";
+      default:
+        throw UnimplementedError;
+    }
+  }
+
+  String title() {
+    switch (this) {
+      case FieldType.Checkbox:
+        return LocaleKeys.grid_field_checkboxFieldName.tr();
+      case FieldType.DateTime:
+        return LocaleKeys.grid_field_dateFieldName.tr();
+      case FieldType.MultiSelect:
+        return LocaleKeys.grid_field_multiSelectFieldName.tr();
+      case FieldType.Number:
+        return LocaleKeys.grid_field_numberFieldName.tr();
+      case FieldType.RichText:
+        return LocaleKeys.grid_field_textFieldName.tr();
+      case FieldType.SingleSelect:
+        return LocaleKeys.grid_field_singleSelectFieldName.tr();
+      default:
+        throw UnimplementedError;
+    }
+  }
+}

+ 1 - 42
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_list.dart → frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_type_list.dart

@@ -8,8 +8,7 @@ import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/meta.pb.dart';
 import 'package:flutter/material.dart';
-import 'package:app_flowy/generated/locale_keys.g.dart';
-import 'package:easy_localization/easy_localization.dart';
+import 'field_type_extension.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
 typedef SelectFieldCallback = void Function(FieldType);
@@ -76,43 +75,3 @@ class FieldTypeCell extends StatelessWidget {
     );
   }
 }
-
-extension FieldTypeListExtension on FieldType {
-  String iconName() {
-    switch (this) {
-      case FieldType.Checkbox:
-        return "grid/field/checkbox";
-      case FieldType.DateTime:
-        return "grid/field/date";
-      case FieldType.MultiSelect:
-        return "grid/field/multi_select";
-      case FieldType.Number:
-        return "grid/field/number";
-      case FieldType.RichText:
-        return "grid/field/text";
-      case FieldType.SingleSelect:
-        return "grid/field/single_select";
-      default:
-        throw UnimplementedError;
-    }
-  }
-
-  String title() {
-    switch (this) {
-      case FieldType.Checkbox:
-        return LocaleKeys.grid_field_checkboxFieldName.tr();
-      case FieldType.DateTime:
-        return LocaleKeys.grid_field_dateFieldName.tr();
-      case FieldType.MultiSelect:
-        return LocaleKeys.grid_field_multiSelectFieldName.tr();
-      case FieldType.Number:
-        return LocaleKeys.grid_field_numberFieldName.tr();
-      case FieldType.RichText:
-        return LocaleKeys.grid_field_textFieldName.tr();
-      case FieldType.SingleSelect:
-        return LocaleKeys.grid_field_singleSelectFieldName.tr();
-      default:
-        throw UnimplementedError;
-    }
-  }
-}

+ 0 - 105
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_field_action_sheet.dart

@@ -1,105 +0,0 @@
-import 'package:app_flowy/startup/startup.dart';
-import 'package:app_flowy/workspace/application/grid/prelude.dart';
-import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
-import 'package:flowy_infra/theme.dart';
-import 'package:flowy_infra_ui/flowy_infra_ui.dart';
-import 'package:flowy_infra_ui/style_widget/button.dart';
-import 'package:flowy_infra_ui/style_widget/text.dart';
-import 'package:flowy_infra_ui/widget/spacing.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-import 'field_operation_list.dart';
-import 'package:easy_localization/easy_localization.dart';
-import 'package:app_flowy/generated/locale_keys.g.dart';
-
-class GridFieldActionSheet extends StatelessWidget with FlowyOverlayDelegate {
-  final GridFieldData fieldData;
-  final VoidCallback onEdited;
-  const GridFieldActionSheet({required this.fieldData, required this.onEdited, Key? key}) : super(key: key);
-
-  void show(BuildContext overlayContext) {
-    FlowyOverlay.of(overlayContext).insertWithAnchor(
-      widget: OverlayContainer(
-        child: this,
-        constraints: BoxConstraints.loose(const Size(240, 200)),
-      ),
-      identifier: identifier(),
-      anchorContext: overlayContext,
-      anchorDirection: AnchorDirection.bottomWithLeftAligned,
-      delegate: this,
-    );
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    return BlocProvider(
-      create: (context) => getIt<GridFieldBloc>(param1: fieldData),
-      child: SingleChildScrollView(
-        child: Column(
-          children: [
-            _EditFieldButton(
-              onEdited: () {
-                FlowyOverlay.of(context).remove(identifier());
-                onEdited();
-              },
-            ),
-            const VSpace(6),
-            _FieldOperationList(fieldData, () => FlowyOverlay.of(context).remove(identifier())),
-          ],
-        ),
-      ),
-    );
-  }
-
-  String identifier() {
-    return toString();
-  }
-
-  @override
-  bool asBarrier() {
-    return true;
-  }
-}
-
-class _EditFieldButton extends StatelessWidget {
-  final Function() onEdited;
-  const _EditFieldButton({required this.onEdited, Key? key}) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    final theme = context.watch<AppTheme>();
-    return BlocBuilder<GridFieldBloc, GridFieldState>(
-      builder: (context, state) {
-        return SizedBox(
-          height: GridSize.typeOptionItemHeight,
-          child: FlowyButton(
-            text: FlowyText.medium(LocaleKeys.grid_field_editProperty.tr(), fontSize: 12),
-            hoverColor: theme.hover,
-            onTap: onEdited,
-          ),
-        );
-      },
-    );
-  }
-}
-
-class _FieldOperationList extends StatelessWidget {
-  final GridFieldData fieldData;
-  final VoidCallback onDismissed;
-  const _FieldOperationList(this.fieldData, this.onDismissed, {Key? key}) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    final actions = FieldAction.values
-        .map(
-          (action) => FieldActionCell(
-            fieldId: fieldData.field.id,
-            action: action,
-            onTap: onDismissed,
-          ),
-        )
-        .toList();
-
-    return FieldOperationList(actions: actions);
-  }
-}

+ 4 - 4
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header.dart

@@ -9,8 +9,8 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' hide Row;
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
-import 'grid_field_editor.dart';
-import 'grid_header_cell.dart';
+import 'field_editor.dart';
+import 'field_cell.dart';
 
 class GridHeaderDelegate extends SliverPersistentHeaderDelegate {
   final String gridId;
@@ -55,8 +55,8 @@ class GridHeader extends StatelessWidget {
       child: BlocBuilder<GridHeaderBloc, GridHeaderState>(
         builder: (context, state) {
           final cells = state.fields.map(
-            (field) => GridHeaderCell(
-              GridFieldData(gridId: gridId, field: field),
+            (field) => GridFieldCell(
+              GridFieldCellContext(gridId: gridId, field: field),
             ),
           );
 

+ 0 - 45
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/grid_header_cell.dart

@@ -1,45 +0,0 @@
-import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
-import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
-import 'package:flowy_infra/image.dart';
-import 'package:flowy_infra/theme.dart';
-import 'package:flowy_infra_ui/style_widget/button.dart';
-import 'package:flowy_infra_ui/style_widget/text.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-
-import 'grid_field_editor.dart';
-import 'grid_field_action_sheet.dart';
-
-class GridHeaderCell extends StatelessWidget {
-  final GridFieldData fieldData;
-  const GridHeaderCell(this.fieldData, {Key? key}) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    final theme = context.watch<AppTheme>();
-    final button = FlowyButton(
-      hoverColor: theme.hover,
-      onTap: () => GridFieldActionSheet(
-        fieldData: fieldData,
-        onEdited: () {
-          FieldEditor(
-            gridId: fieldData.gridId,
-            fieldContextLoader: FieldContextLoaderAdaptor(fieldData),
-          ).show(context);
-        },
-      ).show(context),
-      rightIcon: svgWidget("editor/details", color: theme.iconColor),
-      text: Padding(padding: GridSize.cellContentInsets, child: FlowyText.medium(fieldData.field.name, fontSize: 12)),
-    );
-
-    final borderSide = BorderSide(color: theme.shader4, width: 0.4);
-    final decoration = BoxDecoration(border: Border(top: borderSide, right: borderSide, bottom: borderSide));
-
-    return Container(
-      width: fieldData.field.width.toDouble(),
-      decoration: decoration,
-      padding: GridSize.headerContentInsets,
-      child: button,
-    );
-  }
-}

+ 12 - 12
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/type_option/date.dart

@@ -109,8 +109,8 @@ class DateFormatList extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    final formatItems = DateFormat.values.map((format) {
-      return DateFormatItem(
+    final cells = DateFormat.values.map((format) {
+      return DateFormatCell(
           dateFormat: format,
           onSelected: (format) {
             onSelected(format);
@@ -127,9 +127,9 @@ class DateFormatList extends StatelessWidget {
         separatorBuilder: (context, index) {
           return VSpace(GridSize.typeOptionSeparatorHeight);
         },
-        itemCount: formatItems.length,
+        itemCount: cells.length,
         itemBuilder: (BuildContext context, int index) {
-          return formatItems[index];
+          return cells[index];
         },
       ),
     );
@@ -140,11 +140,11 @@ class DateFormatList extends StatelessWidget {
   }
 }
 
-class DateFormatItem extends StatelessWidget {
+class DateFormatCell extends StatelessWidget {
   final bool isSelected;
   final DateFormat dateFormat;
   final Function(DateFormat format) onSelected;
-  const DateFormatItem({
+  const DateFormatCell({
     required this.dateFormat,
     required this.onSelected,
     required this.isSelected,
@@ -199,8 +199,8 @@ class TimeFormatList extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    final formatItems = TimeFormat.values.map((format) {
-      return TimeFormatItem(
+    final cells = TimeFormat.values.map((format) {
+      return TimeFormatCell(
           isSelected: format == selectedFormat,
           timeFormat: format,
           onSelected: (format) {
@@ -217,9 +217,9 @@ class TimeFormatList extends StatelessWidget {
         separatorBuilder: (context, index) {
           return VSpace(GridSize.typeOptionSeparatorHeight);
         },
-        itemCount: formatItems.length,
+        itemCount: cells.length,
         itemBuilder: (BuildContext context, int index) {
-          return formatItems[index];
+          return cells[index];
         },
       ),
     );
@@ -230,11 +230,11 @@ class TimeFormatList extends StatelessWidget {
   }
 }
 
-class TimeFormatItem extends StatelessWidget {
+class TimeFormatCell extends StatelessWidget {
   final TimeFormat timeFormat;
   final bool isSelected;
   final Function(TimeFormat format) onSelected;
-  const TimeFormatItem({
+  const TimeFormatCell({
     required this.timeFormat,
     required this.onSelected,
     required this.isSelected,

+ 109 - 5
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/toolbar/grid_property.dart

@@ -1,19 +1,123 @@
+import 'package:app_flowy/startup/startup.dart';
+import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
+import 'package:app_flowy/workspace/application/grid/setting/property_bloc.dart';
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart';
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_type_extension.dart';
+import 'package:flowy_infra/image.dart';
+import 'package:flowy_infra/theme.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flowy_infra_ui/style_widget/button.dart';
+import 'package:flowy_infra_ui/style_widget/icon_button.dart';
+import 'package:flowy_infra_ui/style_widget/text.dart';
+import 'package:flowy_infra_ui/widget/spacing.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Field;
 import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:styled_widget/styled_widget.dart';
 
-class GridPropertyList extends StatelessWidget {
-  const GridPropertyList({Key? key}) : super(key: key);
+class GridPropertyList extends StatelessWidget with FlowyOverlayDelegate {
+  final String gridId;
+  final List<Field> fields;
+  const GridPropertyList({
+    required this.gridId,
+    required this.fields,
+    Key? key,
+  }) : super(key: key);
+
+  void show(BuildContext context) {
+    FlowyOverlay.of(context).insertWithAnchor(
+      widget: OverlayContainer(
+        child: this,
+        constraints: BoxConstraints.loose(const Size(260, 400)),
+      ),
+      identifier: identifier(),
+      anchorContext: context,
+      anchorDirection: AnchorDirection.bottomRight,
+      style: FlowyOverlayStyle(blur: false),
+      delegate: this,
+    );
+  }
 
   @override
   Widget build(BuildContext context) {
-    return Container();
+    return BlocProvider(
+      create: (context) => getIt<GridPropertyBloc>(param1: gridId, param2: fields),
+      child: BlocBuilder<GridPropertyBloc, GridPropertyState>(
+        builder: (context, state) {
+          final cells = state.fields.map((field) {
+            return _GridPropertyCell(gridId: gridId, field: field);
+          }).toList();
+
+          return ListView.separated(
+            shrinkWrap: true,
+            controller: ScrollController(),
+            separatorBuilder: (context, index) {
+              return VSpace(GridSize.typeOptionSeparatorHeight);
+            },
+            itemCount: cells.length,
+            itemBuilder: (BuildContext context, int index) {
+              return cells[index];
+            },
+          );
+        },
+      ),
+    );
+  }
+
+  String identifier() {
+    return toString();
   }
+
+  @override
+  bool asBarrier() => true;
 }
 
 class _GridPropertyCell extends StatelessWidget {
-  const _GridPropertyCell({Key? key}) : super(key: key);
+  final Field field;
+  final String gridId;
+  const _GridPropertyCell({required this.gridId, required this.field, Key? key}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
-    return Container();
+    final theme = context.watch<AppTheme>();
+
+    final checkmark = field.visibility
+        ? svgWidget('home/show', color: theme.iconColor)
+        : svgWidget('home/hide', color: theme.iconColor);
+
+    return Row(
+      children: [
+        Expanded(
+          child: SizedBox(
+            height: GridSize.typeOptionItemHeight,
+            child: FlowyButton(
+              text: FlowyText.medium(field.name, fontSize: 12),
+              hoverColor: theme.hover,
+              leftIcon: svgWidget(field.fieldType.iconName(), color: theme.iconColor),
+              onTap: () {
+                final fieldCellContext = GridFieldCellContext(
+                  gridId: gridId,
+                  field: field,
+                );
+
+                FieldEditor(
+                  gridId: gridId,
+                  fieldContextLoader: FieldContextLoaderAdaptor(fieldCellContext),
+                ).show(context, anchorDirection: AnchorDirection.bottomRight);
+              },
+            ),
+          ),
+        ),
+        FlowyIconButton(
+          hoverColor: theme.hover,
+          width: GridSize.typeOptionItemHeight,
+          onPressed: () {
+            context.read<GridPropertyBloc>().add(GridPropertyEvent.setFieldVisibility(field.id, !field.visibility));
+          },
+          icon: checkmark.padding(all: 6),
+        )
+      ],
+    );
   }
 }

+ 31 - 17
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/toolbar/grid_setting.dart

@@ -7,34 +7,57 @@ import 'package:flowy_infra_ui/style_widget/button.dart';
 import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
 import 'package:app_flowy/generated/locale_keys.g.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
 
+import 'grid_property.dart';
+
 class GridSettingContext {
   final String gridId;
+  final List<Field> fields;
+
   GridSettingContext({
     required this.gridId,
+    required this.fields,
   });
 }
 
-class GridSettingList extends StatelessWidget with FlowyOverlayDelegate {
+class GridSettingList extends StatelessWidget {
   final GridSettingContext settingContext;
-  const GridSettingList({required this.settingContext, Key? key}) : super(key: key);
+  final Function(GridSettingAction, GridSettingContext) onAction;
+  const GridSettingList({required this.settingContext, required this.onAction, Key? key}) : super(key: key);
+
+  static void show(BuildContext context, GridSettingContext settingContext) {
+    final list = GridSettingList(
+      settingContext: settingContext,
+      onAction: (action, settingContext) {
+        switch (action) {
+          case GridSettingAction.filter:
+            // TODO: Handle this case.
+            break;
+          case GridSettingAction.sortBy:
+            // TODO: Handle this case.
+            break;
+          case GridSettingAction.properties:
+            GridPropertyList(gridId: settingContext.gridId, fields: settingContext.fields).show(context);
+            break;
+        }
+      },
+    );
 
-  void show(BuildContext context) {
     FlowyOverlay.of(context).insertWithAnchor(
       widget: OverlayContainer(
-        child: this,
+        child: list,
         constraints: BoxConstraints.loose(const Size(140, 400)),
       ),
-      identifier: identifier(),
+      identifier: list.identifier(),
       anchorContext: context,
       anchorDirection: AnchorDirection.bottomRight,
       style: FlowyOverlayStyle(blur: false),
-      delegate: this,
     );
   }
 
@@ -46,17 +69,8 @@ class GridSettingList extends StatelessWidget with FlowyOverlayDelegate {
         listenWhen: (previous, current) => previous.selectedAction != current.selectedAction,
         listener: (context, state) {
           state.selectedAction.foldLeft(null, (_, action) {
-            switch (action) {
-              case GridSettingAction.filter:
-                // TODO: Handle this case.
-                break;
-              case GridSettingAction.sortBy:
-                // TODO: Handle this case.
-                break;
-              case GridSettingAction.properties:
-                // TODO: Handle this case.
-                break;
-            }
+            FlowyOverlay.of(context).remove(identifier());
+            onAction(action, settingContext);
           });
         },
         child: BlocBuilder<GridSettingBloc, GridSettingState>(

+ 22 - 7
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/toolbar/grid_toolbar.dart

@@ -1,25 +1,40 @@
-import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
-import 'package:flowy_infra_ui/style_widget/extension.dart';
-import 'package:flutter/material.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
+import 'package:flowy_infra_ui/style_widget/extension.dart';
 import 'package:flowy_infra_ui/style_widget/icon_button.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' hide Row;
+import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
+
 import 'grid_setting.dart';
 
-class GridToolbar extends StatelessWidget {
+class GridToolbarContext {
   final String gridId;
-  const GridToolbar({required this.gridId, Key? key}) : super(key: key);
+  final List<Field> fields;
+  GridToolbarContext({
+    required this.gridId,
+    required this.fields,
+  });
+}
+
+class GridToolbar extends StatelessWidget {
+  final GridToolbarContext toolbarContext;
+  const GridToolbar({required this.toolbarContext, Key? key}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
+    final settingContext = GridSettingContext(
+      gridId: toolbarContext.gridId,
+      fields: toolbarContext.fields,
+    );
     return SizedBox(
       height: 40,
       child: Row(
         children: [
           SizedBox(width: GridSize.leadingHeaderPadding),
-          _SettingButton(settingContext: GridSettingContext(gridId: gridId)),
+          _SettingButton(settingContext: settingContext),
           const Spacer(),
         ],
       ),
@@ -37,7 +52,7 @@ class _SettingButton extends StatelessWidget {
     return FlowyIconButton(
       hoverColor: theme.hover,
       width: 22,
-      onPressed: () => GridSettingList(settingContext: settingContext).show(context),
+      onPressed: () => GridSettingList.show(context, settingContext),
       icon: svgWidget("grid/setting/setting").padding(horizontal: 3, vertical: 3),
     );
   }

+ 1 - 1
frontend/rust-lib/flowy-grid/src/services/grid_editor.rs

@@ -295,7 +295,7 @@ impl ClientGridEditor {
 
     pub async fn get_field_metas(&self, field_orders: Option<RepeatedFieldOrder>) -> FlowyResult<Vec<FieldMeta>> {
         let mut field_metas = self.pad.read().await.get_field_metas(field_orders)?;
-        field_metas.retain(|field_meta| field_meta.visibility);
+        // field_metas.retain(|field_meta| field_meta.visibility);
         Ok(field_metas)
     }