nathan 2 роки тому
батько
коміт
b4671c1d99
37 змінених файлів з 431 додано та 28 видалено
  1. 1 0
      frontend/app_flowy/assets/translations/en.json
  2. 0 8
      frontend/app_flowy/lib/plugins/board/application/group.dart
  3. 2 0
      frontend/app_flowy/lib/plugins/board/presentation/board_page.dart
  4. 10 0
      frontend/app_flowy/lib/plugins/board/presentation/card/board_checklist_cell.dart
  5. 5 0
      frontend/app_flowy/lib/plugins/board/presentation/card/card_cell_builder.dart
  6. 1 0
      frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_controller.dart
  7. 1 11
      frontend/app_flowy/lib/plugins/grid/application/field/field_controller.dart
  8. 12 0
      frontend/app_flowy/lib/plugins/grid/application/field/type_option/type_option_context.dart
  9. 6 0
      frontend/app_flowy/lib/plugins/grid/application/filter/filter_create_bloc.dart
  10. 19 0
      frontend/app_flowy/lib/plugins/grid/application/filter/filter_service.dart
  11. 5 0
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_builder.dart
  12. 17 0
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/checklist_cell.dart
  13. 14 0
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/choicechip/checklist.dart
  14. 3 0
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/menu_item.dart
  15. 4 0
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_type_extension.dart
  16. 16 0
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/builder.dart
  17. 11 0
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/checklist.dart
  18. 4 0
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_detail.dart
  19. 8 1
      frontend/rust-lib/flowy-grid/src/entities/field_entities.rs
  20. 55 0
      frontend/rust-lib/flowy-grid/src/entities/filter_entities/checklist_filter.rs
  21. 2 0
      frontend/rust-lib/flowy-grid/src/entities/filter_entities/mod.rs
  22. 4 2
      frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs
  23. 3 0
      frontend/rust-lib/flowy-grid/src/services/cell/any_cell_data.rs
  24. 7 0
      frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs
  25. 3 0
      frontend/rust-lib/flowy-grid/src/services/field/type_option_builder.rs
  26. 8 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/checklist_filter.rs
  27. 114 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/checklist_type_option.rs
  28. 5 0
      frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/mod.rs
  29. 1 1
      frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/multi_select_type_option.rs
  30. 12 2
      frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_filter.rs
  31. 8 1
      frontend/rust-lib/flowy-grid/src/services/filter/cache.rs
  32. 14 0
      frontend/rust-lib/flowy-grid/src/services/filter/controller.rs
  33. 6 0
      frontend/rust-lib/flowy-grid/src/services/group/group_util.rs
  34. 18 0
      frontend/rust-lib/flowy-grid/tests/grid/block_test/script.rs
  35. 14 1
      frontend/rust-lib/flowy-grid/tests/grid/block_test/util.rs
  36. 5 1
      frontend/rust-lib/flowy-grid/tests/grid/cell_test/test.rs
  37. 13 0
      frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs

+ 1 - 0
frontend/app_flowy/assets/translations/en.json

@@ -222,6 +222,7 @@
       "singleSelectFieldName": "Select",
       "multiSelectFieldName": "Multiselect",
       "urlFieldName": "URL",
+      "checklistFieldName": "Checklist",
       "numberFormat": " Number format",
       "dateFormat": " Date format",
       "includeTime": " Include time",

+ 0 - 8
frontend/app_flowy/lib/plugins/board/application/group.dart

@@ -10,11 +10,3 @@ class BoardGroupService {
     groupField = field;
   }
 }
-
-abstract class CanBeGroupField {
-  String get groupContent;
-}
-
-// class SingleSelectGroup extends CanBeGroupField {
-//   final SingleSelectTypeOptionContext typeOptionContext;
-// }

+ 2 - 0
frontend/app_flowy/lib/plugins/board/presentation/board_page.dart

@@ -358,6 +358,8 @@ Widget? _buildHeaderIcon(GroupData customData) {
       break;
     case FieldType.URL:
       break;
+    case FieldType.CheckList:
+      break;
   }
 
   if (widget != null) {

+ 10 - 0
frontend/app_flowy/lib/plugins/board/presentation/card/board_checklist_cell.dart

@@ -0,0 +1,10 @@
+import 'package:flutter/material.dart';
+
+class BoardChecklistCell extends StatelessWidget {
+  const BoardChecklistCell({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Container();
+  }
+}

+ 5 - 0
frontend/app_flowy/lib/plugins/board/presentation/card/card_cell_builder.dart

@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
 
 import 'board_cell.dart';
 import 'board_checkbox_cell.dart';
+import 'board_checklist_cell.dart';
 import 'board_date_cell.dart';
 import 'board_number_cell.dart';
 import 'board_select_option_cell.dart';
@@ -58,6 +59,10 @@ class BoardCellBuilder {
           editableNotifier: cellNotifier,
           key: key,
         );
+      case FieldType.CheckList:
+        return BoardChecklistCell(
+          key: key,
+        );
       case FieldType.Number:
         return BoardNumberCell(
           groupId: groupId,

+ 1 - 0
frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_controller.dart

@@ -81,6 +81,7 @@ class GridCellControllerBuilder {
         );
       case FieldType.MultiSelect:
       case FieldType.SingleSelect:
+      case FieldType.CheckList:
         final cellDataLoader = GridCellDataLoader(
           cellId: _cellId,
           parser: SelectOptionCellDataParser(),

+ 1 - 11
frontend/app_flowy/lib/plugins/grid/application/field/field_controller.dart

@@ -506,22 +506,12 @@ class FieldInfo {
   bool get canGroup {
     switch (_field.fieldType) {
       case FieldType.Checkbox:
-        return true;
-      case FieldType.DateTime:
-        return false;
       case FieldType.MultiSelect:
-        return true;
-      case FieldType.Number:
-        return false;
-      case FieldType.RichText:
-        return false;
       case FieldType.SingleSelect:
         return true;
-      case FieldType.URL:
+      default:
         return false;
     }
-
-    return false;
   }
 
   bool get canCreateFilter {

+ 12 - 0
frontend/app_flowy/lib/plugins/grid/application/field/type_option/type_option_context.dart

@@ -1,6 +1,7 @@
 import 'package:flowy_sdk/dispatch/dispatch.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_type_option.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/checklist_type_option.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
 import 'package:dartz/dartz.dart';
@@ -95,6 +96,17 @@ class MultiSelectTypeOptionWidgetDataParser
   }
 }
 
+// Multi-select
+typedef ChecklistTypeOptionContext = TypeOptionContext<ChecklistTypeOptionPB>;
+
+class ChecklistTypeOptionWidgetDataParser
+    extends TypeOptionDataParser<ChecklistTypeOptionPB> {
+  @override
+  ChecklistTypeOptionPB fromBuffer(List<int> buffer) {
+    return ChecklistTypeOptionPB.fromBuffer(buffer);
+  }
+}
+
 class TypeOptionContext<T extends GeneratedMessage> {
   T? _typeOptionObject;
   final TypeOptionDataParser<T> dataParser;

+ 6 - 0
frontend/app_flowy/lib/plugins/grid/application/filter/filter_create_bloc.dart

@@ -2,6 +2,7 @@ import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
 import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pbserver.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_filter.pbenum.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/checklist_filter.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/date_filter.pbenum.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/number_filter.pb.dart';
@@ -104,6 +105,11 @@ class GridCreateFilterBloc
           condition: SelectOptionCondition.OptionIs,
           fieldType: FieldType.MultiSelect,
         );
+      case FieldType.CheckList:
+        return _ffiService.insertChecklistFilter(
+          fieldId: fieldId,
+          condition: ChecklistFilterCondition.IsIncomplete,
+        );
       case FieldType.Number:
         return _ffiService.insertNumberFilter(
           fieldId: fieldId,

+ 19 - 0
frontend/app_flowy/lib/plugins/grid/application/filter/filter_service.dart

@@ -3,6 +3,7 @@ import 'package:flowy_sdk/dispatch/dispatch.dart';
 import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_filter.pbserver.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/checklist_filter.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/date_filter.pbserver.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/grid_entities.pb.dart';
@@ -145,6 +146,24 @@ class FilterFFIService {
     );
   }
 
+  Future<Either<Unit, FlowyError>> insertChecklistFilter({
+    required String fieldId,
+    required ChecklistFilterCondition condition,
+    String? filterId,
+    List<String> optionIds = const [],
+  }) {
+    final filter = ChecklistFilterPB()
+      ..condition = condition
+      ..optionIds.addAll(optionIds);
+
+    return insertFilter(
+      fieldId: fieldId,
+      filterId: filterId,
+      fieldType: FieldType.CheckList,
+      data: filter.writeToBuffer(),
+    );
+  }
+
   Future<Either<Unit, FlowyError>> insertFilter({
     required String fieldId,
     String? filterId,

+ 5 - 0
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_builder.dart

@@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
 import 'cell_accessory.dart';
 import 'cell_shortcuts.dart';
 import 'checkbox_cell.dart';
+import 'checklist_cell.dart';
 import 'date_cell/date_cell.dart';
 import 'number_cell.dart';
 import 'select_option_cell/select_option_cell.dart';
@@ -55,6 +56,10 @@ class GridCellBuilder {
           style: style,
           key: key,
         );
+      case FieldType.CheckList:
+        return GridChecklistCell(
+          key: key,
+        );
       case FieldType.Number:
         return GridNumberCell(
           cellControllerBuilder: cellControllerBuilder,

+ 17 - 0
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/checklist_cell.dart

@@ -0,0 +1,17 @@
+import 'package:flutter/material.dart';
+
+import 'cell_builder.dart';
+
+class GridChecklistCell extends GridCellWidget {
+  GridChecklistCell({Key? key}) : super(key: key);
+
+  @override
+  ChecklistCellState createState() => ChecklistCellState();
+}
+
+class ChecklistCellState extends State<GridChecklistCell> {
+  @override
+  Widget build(BuildContext context) {
+    return Container();
+  }
+}

+ 14 - 0
frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/choicechip/checklist.dart

@@ -0,0 +1,14 @@
+import 'package:app_flowy/plugins/grid/presentation/widgets/filter/filter_info.dart';
+import 'package:flutter/material.dart';
+import 'choicechip.dart';
+
+class ChecklistFilterChoicechip extends StatelessWidget {
+  final FilterInfo filterInfo;
+  const ChecklistFilterChoicechip({required this.filterInfo, Key? key})
+      : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return ChoiceChipButton(filterInfo: filterInfo);
+  }
+}

+ 3 - 0
frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/menu_item.dart

@@ -2,6 +2,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pbenum.dart';
 import 'package:flutter/material.dart';
 
 import 'choicechip/checkbox.dart';
+import 'choicechip/checklist.dart';
 import 'choicechip/date.dart';
 import 'choicechip/number.dart';
 import 'choicechip/select_option/select_option.dart';
@@ -35,6 +36,8 @@ Widget buildFilterChoicechip(FilterInfo filterInfo) {
       return SelectOptionFilterChoicechip(filterInfo: filterInfo);
     case FieldType.URL:
       return URLFilterChoicechip(filterInfo: filterInfo);
+    case FieldType.CheckList:
+      return ChecklistFilterChoicechip(filterInfo: filterInfo);
     default:
       return const SizedBox();
   }

+ 4 - 0
frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_type_extension.dart

@@ -19,6 +19,8 @@ extension FieldTypeListExtension on FieldType {
         return "grid/field/single_select";
       case FieldType.URL:
         return "grid/field/url";
+      case FieldType.CheckList:
+        return "grid/field/checklist";
     }
     throw UnimplementedError;
   }
@@ -39,6 +41,8 @@ extension FieldTypeListExtension on FieldType {
         return LocaleKeys.grid_field_singleSelectFieldName.tr();
       case FieldType.URL:
         return LocaleKeys.grid_field_urlFieldName.tr();
+      case FieldType.CheckList:
+        return LocaleKeys.grid_field_checklistFieldName.tr();
     }
     throw UnimplementedError;
   }

+ 16 - 0
frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/builder.dart

@@ -8,6 +8,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_type_option.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/multi_select_type_option.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/single_select_type_option.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/text_type_option.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart';
@@ -15,6 +16,7 @@ import 'package:protobuf/protobuf.dart' hide FieldInfo;
 import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
 import 'package:flutter/material.dart';
 import 'checkbox.dart';
+import 'checklist.dart';
 import 'date.dart';
 import 'multi_select.dart';
 import 'number.dart';
@@ -124,6 +126,15 @@ TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder({
           dataController: dataController,
         ),
       );
+
+    case FieldType.CheckList:
+      return ChecklistTypeOptionWidgetBuilder(
+        makeTypeOptionContextWithDataController<ChecklistTypeOptionPB>(
+          gridId: gridId,
+          fieldType: fieldType,
+          dataController: dataController,
+        ),
+      );
   }
   throw UnimplementedError;
 }
@@ -206,6 +217,11 @@ TypeOptionContext<T>
         dataController: dataController,
         dataParser: MultiSelectTypeOptionWidgetDataParser(),
       ) as TypeOptionContext<T>;
+    case FieldType.CheckList:
+      return ChecklistTypeOptionContext(
+        dataController: dataController,
+        dataParser: ChecklistTypeOptionWidgetDataParser(),
+      ) as TypeOptionContext<T>;
     case FieldType.Number:
       return NumberTypeOptionContext(
         dataController: dataController,

+ 11 - 0
frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/checklist.dart

@@ -0,0 +1,11 @@
+import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
+import 'package:flutter/material.dart';
+import 'builder.dart';
+
+class ChecklistTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
+  ChecklistTypeOptionWidgetBuilder(
+      ChecklistTypeOptionContext typeOptionContext);
+
+  @override
+  Widget? build(BuildContext context) => null;
+}

+ 4 - 0
frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_detail.dart

@@ -331,6 +331,10 @@ GridCellStyle? _customCellStyle(FieldType fieldType) {
       return SelectOptionCellStyle(
         placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),
       );
+    case FieldType.CheckList:
+      return SelectOptionCellStyle(
+        placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),
+      );
     case FieldType.Number:
       return null;
     case FieldType.RichText:

+ 8 - 1
frontend/rust-lib/flowy-grid/src/entities/field_entities.rs

@@ -491,6 +491,7 @@ pub enum FieldType {
     MultiSelect = 4,
     Checkbox = 5,
     URL = 6,
+    CheckList = 7,
 }
 
 pub const RICH_TEXT_FIELD: FieldType = FieldType::RichText;
@@ -500,6 +501,7 @@ pub const SINGLE_SELECT_FIELD: FieldType = FieldType::SingleSelect;
 pub const MULTI_SELECT_FIELD: FieldType = FieldType::MultiSelect;
 pub const CHECKBOX_FIELD: FieldType = FieldType::Checkbox;
 pub const URL_FIELD: FieldType = FieldType::URL;
+pub const CHECKLIST_FIELD: FieldType = FieldType::CheckList;
 
 impl std::default::Default for FieldType {
     fn default() -> Self {
@@ -563,6 +565,10 @@ impl FieldType {
         self == &MULTI_SELECT_FIELD || self == &SINGLE_SELECT_FIELD
     }
 
+    pub fn is_check_list(&self) -> bool {
+        self == &CHECKLIST_FIELD
+    }
+
     pub fn can_be_group(&self) -> bool {
         self.is_select_option()
     }
@@ -596,8 +602,9 @@ impl std::convert::From<FieldTypeRevision> for FieldType {
             4 => FieldType::MultiSelect,
             5 => FieldType::Checkbox,
             6 => FieldType::URL,
+            7 => FieldType::CheckList,
             _ => {
-                tracing::error!("Can't parser FieldTypeRevision: {} to FieldType", ty);
+                tracing::error!("Can't convert FieldTypeRevision: {} to FieldType", ty);
                 FieldType::RichText
             }
         }

+ 55 - 0
frontend/rust-lib/flowy-grid/src/entities/filter_entities/checklist_filter.rs

@@ -0,0 +1,55 @@
+use crate::services::field::SelectOptionIds;
+use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
+use flowy_error::ErrorCode;
+use grid_rev_model::FilterRevision;
+
+#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
+pub struct ChecklistFilterPB {
+    #[pb(index = 1)]
+    pub condition: ChecklistFilterCondition,
+
+    #[pb(index = 2)]
+    pub option_ids: Vec<String>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
+#[repr(u8)]
+pub enum ChecklistFilterCondition {
+    IsComplete = 0,
+    IsIncomplete = 1,
+}
+
+impl std::convert::From<ChecklistFilterCondition> for u32 {
+    fn from(value: ChecklistFilterCondition) -> Self {
+        value as u32
+    }
+}
+
+impl std::default::Default for ChecklistFilterCondition {
+    fn default() -> Self {
+        ChecklistFilterCondition::IsIncomplete
+    }
+}
+
+impl std::convert::TryFrom<u8> for ChecklistFilterCondition {
+    type Error = ErrorCode;
+
+    fn try_from(value: u8) -> Result<Self, Self::Error> {
+        match value {
+            0 => Ok(ChecklistFilterCondition::IsComplete),
+            1 => Ok(ChecklistFilterCondition::IsIncomplete),
+            _ => Err(ErrorCode::InvalidData),
+        }
+    }
+}
+
+impl std::convert::From<&FilterRevision> for ChecklistFilterPB {
+    fn from(rev: &FilterRevision) -> Self {
+        let ids = SelectOptionIds::from(rev.content.clone());
+        ChecklistFilterPB {
+            condition: ChecklistFilterCondition::try_from(rev.condition)
+                .unwrap_or(ChecklistFilterCondition::IsIncomplete),
+            option_ids: ids.into_inner(),
+        }
+    }
+}

+ 2 - 0
frontend/rust-lib/flowy-grid/src/entities/filter_entities/mod.rs

@@ -1,4 +1,5 @@
 mod checkbox_filter;
+mod checklist_filter;
 mod date_filter;
 mod filter_changeset;
 mod number_filter;
@@ -7,6 +8,7 @@ mod text_filter;
 mod util;
 
 pub use checkbox_filter::*;
+pub use checklist_filter::*;
 pub use date_filter::*;
 pub use filter_changeset::*;
 pub use number_filter::*;

+ 4 - 2
frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs

@@ -1,6 +1,7 @@
 use crate::entities::parser::NotEmptyStr;
 use crate::entities::{
-    CheckboxFilterPB, DateFilterContent, DateFilterPB, FieldType, NumberFilterPB, SelectOptionFilterPB, TextFilterPB,
+    CheckboxFilterPB, ChecklistFilterPB, DateFilterContent, DateFilterPB, FieldType, NumberFilterPB,
+    SelectOptionFilterPB, TextFilterPB,
 };
 use crate::services::field::SelectOptionIds;
 use crate::services::filter::FilterType;
@@ -35,6 +36,7 @@ impl std::convert::From<&FilterRevision> for FilterPB {
             FieldType::DateTime => DateFilterPB::from(rev).try_into().unwrap(),
             FieldType::SingleSelect => SelectOptionFilterPB::from(rev).try_into().unwrap(),
             FieldType::MultiSelect => SelectOptionFilterPB::from(rev).try_into().unwrap(),
+            FieldType::CheckList => ChecklistFilterPB::from(rev).try_into().unwrap(),
             FieldType::Checkbox => CheckboxFilterPB::from(rev).try_into().unwrap(),
             FieldType::URL => TextFilterPB::from(rev).try_into().unwrap(),
         };
@@ -174,7 +176,7 @@ impl TryInto<AlterFilterParams> for AlterFilterPayloadPB {
                 }
                 .to_string();
             }
-            FieldType::SingleSelect | FieldType::MultiSelect => {
+            FieldType::SingleSelect | FieldType::MultiSelect | FieldType::CheckList => {
                 let filter = SelectOptionFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?;
                 condition = filter.condition as u8;
                 content = SelectOptionIds::from(filter.option_ids).to_string();

+ 3 - 0
frontend/rust-lib/flowy-grid/src/services/cell/any_cell_data.rs

@@ -104,6 +104,9 @@ impl TypeCellData {
     pub fn is_multi_select(&self) -> bool {
         self.field_type == FieldType::MultiSelect
     }
+    pub fn is_checklist(&self) -> bool {
+        self.field_type == FieldType::CheckList
+    }
 
     pub fn is_url(&self) -> bool {
         self.field_type == FieldType::URL

+ 7 - 0
frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs

@@ -122,6 +122,7 @@ pub fn apply_cell_data_changeset<C: ToString, T: AsRef<FieldRevision>>(
             SingleSelectTypeOptionPB::from(field_rev).apply_changeset(changeset.into(), cell_rev)
         }
         FieldType::MultiSelect => MultiSelectTypeOptionPB::from(field_rev).apply_changeset(changeset.into(), cell_rev),
+        FieldType::CheckList => ChecklistTypeOptionPB::from(field_rev).apply_changeset(changeset.into(), cell_rev),
         FieldType::Checkbox => CheckboxTypeOptionPB::from(field_rev).apply_changeset(changeset.into(), cell_rev),
         FieldType::URL => URLTypeOptionPB::from(field_rev).apply_changeset(changeset.into(), cell_rev),
     }?;
@@ -180,6 +181,9 @@ pub fn decode_cell_data_to_string(
             FieldType::MultiSelect => field_rev
                 .get_type_option::<MultiSelectTypeOptionPB>(field_type)?
                 .displayed_cell_string(cell_data.into(), from_field_type, field_rev),
+            FieldType::CheckList => field_rev
+                .get_type_option::<ChecklistTypeOptionPB>(field_type)?
+                .displayed_cell_string(cell_data.into(), from_field_type, field_rev),
             FieldType::Checkbox => field_rev
                 .get_type_option::<CheckboxTypeOptionPB>(field_type)?
                 .displayed_cell_string(cell_data.into(), from_field_type, field_rev),
@@ -230,6 +234,9 @@ pub fn try_decode_cell_data(
             FieldType::MultiSelect => field_rev
                 .get_type_option::<MultiSelectTypeOptionPB>(field_type)?
                 .decode_cell_data(cell_data.into(), from_field_type, field_rev),
+            FieldType::CheckList => field_rev
+                .get_type_option::<ChecklistTypeOptionPB>(field_type)?
+                .decode_cell_data(cell_data.into(), from_field_type, field_rev),
             FieldType::Checkbox => field_rev
                 .get_type_option::<CheckboxTypeOptionPB>(field_type)?
                 .decode_cell_data(cell_data.into(), from_field_type, field_rev),

+ 3 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_option_builder.rs

@@ -38,6 +38,7 @@ pub fn default_type_option_builder_from_type(field_type: &FieldType) -> Box<dyn
         FieldType::MultiSelect => MultiSelectTypeOptionPB::default().into(),
         FieldType::Checkbox => CheckboxTypeOptionPB::default().into(),
         FieldType::URL => URLTypeOptionPB::default().into(),
+        FieldType::CheckList => ChecklistTypeOptionPB::default().into(),
     };
 
     type_option_builder_from_json_str(&s, field_type)
@@ -52,6 +53,7 @@ pub fn type_option_builder_from_json_str(s: &str, field_type: &FieldType) -> Box
         FieldType::MultiSelect => Box::new(MultiSelectTypeOptionBuilder::from_json_str(s)),
         FieldType::Checkbox => Box::new(CheckboxTypeOptionBuilder::from_json_str(s)),
         FieldType::URL => Box::new(URLTypeOptionBuilder::from_json_str(s)),
+        FieldType::CheckList => Box::new(ChecklistTypeOptionBuilder::from_json_str(s)),
     }
 }
 
@@ -65,5 +67,6 @@ pub fn type_option_builder_from_bytes<T: Into<Bytes>>(bytes: T, field_type: &Fie
         FieldType::MultiSelect => Box::new(MultiSelectTypeOptionBuilder::from_protobuf_bytes(bytes)),
         FieldType::Checkbox => Box::new(CheckboxTypeOptionBuilder::from_protobuf_bytes(bytes)),
         FieldType::URL => Box::new(URLTypeOptionBuilder::from_protobuf_bytes(bytes)),
+        FieldType::CheckList => Box::new(ChecklistTypeOptionBuilder::from_protobuf_bytes(bytes)),
     }
 }

+ 8 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/checklist_filter.rs

@@ -0,0 +1,8 @@
+use crate::entities::ChecklistFilterPB;
+use crate::services::field::SelectedSelectOptions;
+
+impl ChecklistFilterPB {
+    pub fn is_visible(&self, selected_options: &SelectedSelectOptions) -> bool {
+        true
+    }
+}

+ 114 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/checklist_type_option.rs

@@ -0,0 +1,114 @@
+use crate::entities::FieldType;
+use crate::impl_type_option;
+use crate::services::cell::{AnyCellChangeset, CellBytes, CellData, CellDataOperation, CellDisplayable};
+use crate::services::field::selection_type_option::type_option_transform::SelectOptionTypeOptionTransformer;
+use crate::services::field::type_options::util::get_cell_data;
+use crate::services::field::{
+    BoxTypeOptionBuilder, SelectOptionCellChangeset, SelectOptionIds, SelectOptionPB, SelectTypeOptionSharedAction,
+    TypeOptionBuilder,
+};
+use bytes::Bytes;
+use flowy_derive::ProtoBuf;
+use flowy_error::{FlowyError, FlowyResult};
+use grid_rev_model::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer};
+use serde::{Deserialize, Serialize};
+
+// Multiple select
+#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
+pub struct ChecklistTypeOptionPB {
+    #[pb(index = 1)]
+    pub options: Vec<SelectOptionPB>,
+
+    #[pb(index = 2)]
+    pub disable_color: bool,
+}
+impl_type_option!(ChecklistTypeOptionPB, FieldType::CheckList);
+
+impl SelectTypeOptionSharedAction for ChecklistTypeOptionPB {
+    fn number_of_max_options(&self) -> Option<usize> {
+        None
+    }
+
+    fn options(&self) -> &Vec<SelectOptionPB> {
+        &self.options
+    }
+
+    fn mut_options(&mut self) -> &mut Vec<SelectOptionPB> {
+        &mut self.options
+    }
+}
+
+impl CellDataOperation<SelectOptionIds, SelectOptionCellChangeset> for ChecklistTypeOptionPB {
+    fn decode_cell_data(
+        &self,
+        cell_data: CellData<SelectOptionIds>,
+        decoded_field_type: &FieldType,
+        field_rev: &FieldRevision,
+    ) -> FlowyResult<CellBytes> {
+        self.displayed_cell_bytes(cell_data, decoded_field_type, field_rev)
+    }
+
+    fn apply_changeset(
+        &self,
+        changeset: AnyCellChangeset<SelectOptionCellChangeset>,
+        cell_rev: Option<CellRevision>,
+    ) -> Result<String, FlowyError> {
+        let content_changeset = changeset.try_into_inner()?;
+
+        let insert_option_ids = content_changeset
+            .insert_option_ids
+            .into_iter()
+            .filter(|insert_option_id| self.options.iter().any(|option| &option.id == insert_option_id))
+            .collect::<Vec<String>>();
+
+        let new_cell_data: String;
+        match cell_rev {
+            None => {
+                new_cell_data = SelectOptionIds::from(insert_option_ids).to_string();
+            }
+            Some(cell_rev) => {
+                let cell_data = get_cell_data(&cell_rev);
+                let mut select_ids: SelectOptionIds = cell_data.into();
+                for insert_option_id in insert_option_ids {
+                    if !select_ids.contains(&insert_option_id) {
+                        select_ids.push(insert_option_id);
+                    }
+                }
+
+                for delete_option_id in content_changeset.delete_option_ids {
+                    select_ids.retain(|id| id != &delete_option_id);
+                }
+
+                new_cell_data = select_ids.to_string();
+                tracing::trace!("checklist's cell data: {}", &new_cell_data);
+            }
+        }
+
+        Ok(new_cell_data)
+    }
+}
+
+#[derive(Default)]
+pub struct ChecklistTypeOptionBuilder(ChecklistTypeOptionPB);
+impl_into_box_type_option_builder!(ChecklistTypeOptionBuilder);
+impl_builder_from_json_str_and_from_bytes!(ChecklistTypeOptionBuilder, ChecklistTypeOptionPB);
+impl ChecklistTypeOptionBuilder {
+    pub fn add_option(mut self, opt: SelectOptionPB) -> Self {
+        self.0.options.push(opt);
+        self
+    }
+}
+
+impl TypeOptionBuilder for ChecklistTypeOptionBuilder {
+    fn field_type(&self) -> FieldType {
+        FieldType::CheckList
+    }
+
+    fn serializer(&self) -> &dyn TypeOptionDataSerializer {
+        &self.0
+    }
+
+    fn transform(&mut self, field_type: &FieldType, type_option_data: String) {
+        SelectOptionTypeOptionTransformer::transform_type_option(&mut self.0, field_type, type_option_data)
+    }
+}

+ 5 - 0
frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/mod.rs

@@ -1,9 +1,14 @@
+mod checklist_filter;
+mod checklist_type_option;
 mod multi_select_type_option;
 mod select_filter;
 mod select_type_option;
 mod single_select_type_option;
 mod type_option_transform;
 
+pub use checklist_type_option::*;
 pub use multi_select_type_option::*;
 pub use select_type_option::*;
 pub use single_select_type_option::*;
+
+pub use checklist_filter::*;

+ 1 - 1
frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/multi_select_type_option.rs

@@ -80,7 +80,7 @@ impl CellDataOperation<SelectOptionIds, SelectOptionCellChangeset> for MultiSele
                 }
 
                 new_cell_data = select_ids.to_string();
-                tracing::trace!("Multi select cell data: {}", &new_cell_data);
+                tracing::trace!("Multi-select cell data: {}", &new_cell_data);
             }
         }
 

+ 12 - 2
frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_filter.rs

@@ -1,8 +1,8 @@
 #![allow(clippy::needless_collect)]
 
-use crate::entities::{SelectOptionCondition, SelectOptionFilterPB};
+use crate::entities::{ChecklistFilterPB, SelectOptionCondition, SelectOptionFilterPB};
 use crate::services::cell::{CellFilterOperation, TypeCellData};
-use crate::services::field::{MultiSelectTypeOptionPB, SingleSelectTypeOptionPB};
+use crate::services::field::{ChecklistTypeOptionPB, MultiSelectTypeOptionPB, SingleSelectTypeOptionPB};
 use crate::services::field::{SelectTypeOptionSharedAction, SelectedSelectOptions};
 use flowy_error::FlowyResult;
 
@@ -61,6 +61,16 @@ impl CellFilterOperation<SelectOptionFilterPB> for SingleSelectTypeOptionPB {
     }
 }
 
+impl CellFilterOperation<ChecklistFilterPB> for ChecklistTypeOptionPB {
+    fn apply_filter(&self, any_cell_data: TypeCellData, filter: &ChecklistFilterPB) -> FlowyResult<bool> {
+        if !any_cell_data.is_checklist() {
+            return Ok(true);
+        }
+        let selected_options = SelectedSelectOptions::from(self.get_selected_options(any_cell_data.into()));
+        Ok(filter.is_visible(&selected_options))
+    }
+}
+
 #[cfg(test)]
 mod tests {
     #![allow(clippy::all)]

+ 8 - 1
frontend/rust-lib/flowy-grid/src/services/filter/cache.rs

@@ -1,4 +1,6 @@
-use crate::entities::{CheckboxFilterPB, DateFilterPB, FieldType, NumberFilterPB, SelectOptionFilterPB, TextFilterPB};
+use crate::entities::{
+    CheckboxFilterPB, ChecklistFilterPB, DateFilterPB, FieldType, NumberFilterPB, SelectOptionFilterPB, TextFilterPB,
+};
 use crate::services::filter::FilterType;
 use std::collections::HashMap;
 
@@ -10,6 +12,7 @@ pub(crate) struct FilterMap {
     pub(crate) date_filter: HashMap<FilterType, DateFilterPB>,
     pub(crate) select_option_filter: HashMap<FilterType, SelectOptionFilterPB>,
     pub(crate) checkbox_filter: HashMap<FilterType, CheckboxFilterPB>,
+    pub(crate) checklist_filter: HashMap<FilterType, ChecklistFilterPB>,
 }
 
 impl FilterMap {
@@ -26,6 +29,7 @@ impl FilterMap {
             FieldType::MultiSelect => self.select_option_filter.get(filter_type).is_some(),
             FieldType::Checkbox => self.checkbox_filter.get(filter_type).is_some(),
             FieldType::URL => self.url_filter.get(filter_type).is_some(),
+            FieldType::CheckList => self.checklist_filter.get(filter_type).is_some(),
         }
     }
 
@@ -83,6 +87,9 @@ impl FilterMap {
             FieldType::URL => {
                 let _ = self.url_filter.remove(filter_id);
             }
+            FieldType::CheckList => {
+                let _ = self.checklist_filter.remove(filter_id);
+            }
         };
     }
 }

+ 14 - 0
frontend/rust-lib/flowy-grid/src/services/filter/controller.rs

@@ -291,6 +291,12 @@ impl FilterController {
                             .url_filter
                             .insert(filter_type, TextFilterPB::from(filter_rev.as_ref()));
                     }
+                    FieldType::CheckList => {
+                        let _ = self
+                            .filter_map
+                            .checklist_filter
+                            .insert(filter_type, ChecklistFilterPB::from(filter_rev.as_ref()));
+                    }
                 }
             }
         }
@@ -413,6 +419,14 @@ fn filter_cell(
                     .ok(),
             )
         }),
+        FieldType::CheckList => filter_map.checklist_filter.get(filter_id).and_then(|filter| {
+            Some(
+                field_rev
+                    .get_type_option::<ChecklistTypeOptionPB>(field_rev.ty)?
+                    .apply_filter(any_cell_data, filter)
+                    .ok(),
+            )
+        }),
     }?;
     tracing::Span::current().record(
         "cell_content",

+ 6 - 0
frontend/rust-lib/flowy-grid/src/services/group/group_util.rs

@@ -123,6 +123,12 @@ pub fn default_group_configuration(field_rev: &FieldRevision) -> GroupConfigurat
             SelectOptionGroupConfigurationRevision::default(),
         )
         .unwrap(),
+        FieldType::CheckList => GroupConfigurationRevision::new(
+            field_id,
+            field_type_rev,
+            SelectOptionGroupConfigurationRevision::default(),
+        )
+        .unwrap(),
         FieldType::Checkbox => {
             GroupConfigurationRevision::new(field_id, field_type_rev, CheckboxGroupConfigurationRevision::default())
                 .unwrap()

+ 18 - 0
frontend/rust-lib/flowy-grid/tests/grid/block_test/script.rs

@@ -226,6 +226,24 @@ impl GridRowTest {
                 assert_eq!(s, expected);
             }
 
+            FieldType::CheckList => {
+                let cell_data = self
+                    .editor
+                    .get_cell_bytes(&cell_id)
+                    .await
+                    .unwrap()
+                    .parser::<SelectOptionCellDataParser>()
+                    .unwrap();
+
+                let s = cell_data
+                    .select_options
+                    .into_iter()
+                    .map(|option| option.name)
+                    .collect::<Vec<String>>()
+                    .join(SELECTION_IDS_SEPARATOR);
+
+                assert_eq!(s, expected);
+            }
             FieldType::Checkbox => {
                 let cell_data = self
                     .editor

+ 14 - 1
frontend/rust-lib/flowy-grid/tests/grid/block_test/util.rs

@@ -2,7 +2,7 @@ use flowy_grid::entities::FieldType;
 use std::sync::Arc;
 
 use flowy_grid::services::field::{
-    DateCellChangeset, MultiSelectTypeOptionPB, SelectOptionPB, SingleSelectTypeOptionPB,
+    ChecklistTypeOptionPB, DateCellChangeset, MultiSelectTypeOptionPB, SelectOptionPB, SingleSelectTypeOptionPB,
 };
 use flowy_grid::services::row::RowRevisionBuilder;
 use grid_rev_model::{FieldRevision, RowRevision};
@@ -90,6 +90,19 @@ impl<'a> GridRowTestBuilder<'a> {
         multi_select_field.id.clone()
     }
 
+    pub fn insert_checklist_cell<F>(&mut self, f: F) -> String
+    where
+        F: Fn(Vec<SelectOptionPB>) -> Vec<SelectOptionPB>,
+    {
+        let checklist_field = self.field_rev_with_type(&FieldType::CheckList);
+        let type_option = ChecklistTypeOptionPB::from(&checklist_field);
+        let options = f(type_option.options);
+        let ops_ids = options.iter().map(|option| option.id.clone()).collect::<Vec<_>>();
+        self.inner_builder
+            .insert_select_option_cell(&multi_select_field.id, ops_ids);
+
+        checklist_field.id.clone()
+    }
     pub fn field_rev_with_type(&self, field_type: &FieldType) -> FieldRevision {
         self.field_revs
             .iter()

+ 5 - 1
frontend/rust-lib/flowy-grid/tests/grid/cell_test/test.rs

@@ -3,7 +3,7 @@ use crate::grid::cell_test::script::GridCellTest;
 use crate::grid::field_test::util::make_date_cell_string;
 use flowy_grid::entities::{CellChangesetPB, FieldType};
 use flowy_grid::services::field::selection_type_option::SelectOptionCellChangeset;
-use flowy_grid::services::field::{MultiSelectTypeOptionPB, SingleSelectTypeOptionPB};
+use flowy_grid::services::field::{ChecklistTypeOptionPB, MultiSelectTypeOptionPB, SingleSelectTypeOptionPB};
 
 #[tokio::test]
 async fn grid_cell_update() {
@@ -31,6 +31,10 @@ async fn grid_cell_update() {
                     let type_option = MultiSelectTypeOptionPB::from(field_rev);
                     SelectOptionCellChangeset::from_insert_option_id(&type_option.options.first().unwrap().id).to_str()
                 }
+                FieldType::CheckList => {
+                    let type_option = ChecklistTypeOptionPB::from(field_rev);
+                    SelectOptionCellChangeset::from_insert_option_id(&type_option.options.first().unwrap().id).to_str()
+                }
                 FieldType::Checkbox => "1".to_string(),
                 FieldType::URL => "1".to_string(),
             };

+ 13 - 0
frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs

@@ -153,6 +153,9 @@ pub const COMPLETED: &str = "Completed";
 pub const PLANNED: &str = "Planned";
 pub const PAUSED: &str = "Paused";
 
+pub const FIRST_THING: &str = "Wake up at 6:00 am";
+pub const SECOND_THING: &str = "Get some coffee";
+pub const THIRD_THING: &str = "Start working";
 // This grid is assumed to contain all the Fields.
 fn make_test_grid() -> BuildGridContext {
     let mut grid_builder = GridBuilder::new();
@@ -217,6 +220,14 @@ fn make_test_grid() -> BuildGridContext {
                 let url_field = FieldBuilder::new(url).name("link").visibility(true).build();
                 grid_builder.add_field(url_field);
             }
+            FieldType::CheckList => {
+                let checklist = ChecklistTypeOptionBuilder::default()
+                    .add_option(SelectOptionPB::new(FIRST_THING))
+                    .add_option(SelectOptionPB::new(SECOND_THING))
+                    .add_option(SelectOptionPB::new(THIRD_THING));
+                let checklist_field = FieldBuilder::new(checklist).name("TODO").visibility(true).build();
+                grid_builder.add_field(checklist_field);
+            }
         }
     }
 
@@ -234,6 +245,7 @@ fn make_test_grid() -> BuildGridContext {
                         FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
                         FieldType::MultiSelect => row_builder
                             .insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(0)]),
+                        FieldType::CheckList => row_builder.insert_checklist_cell(|options| options),
                         FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
                         _ => "".to_owned(),
                     };
@@ -370,6 +382,7 @@ fn make_test_board() -> BuildGridContext {
                 let url_field = FieldBuilder::new(url).name("link").visibility(true).build();
                 grid_builder.add_field(url_field);
             }
+            FieldType::CheckList => {}
         }
     }