Przeglądaj źródła

feat: support multi-select option filter (#1501)

Co-authored-by: nathan <[email protected]>
Nathan.fooo 2 lat temu
rodzic
commit
80d1cbabe0
16 zmienionych plików z 147 dodań i 85 usunięć
  1. 1 1
      frontend/app_flowy/lib/plugins/grid/application/field/field_controller.dart
  2. 4 3
      frontend/app_flowy/lib/plugins/grid/application/field/type_option/type_option_context.dart
  3. 2 2
      frontend/app_flowy/lib/plugins/grid/application/field/type_option/type_option_data_controller.dart
  4. 3 3
      frontend/app_flowy/lib/plugins/grid/application/filter/checkbox_filter_editor_bloc.dart
  5. 21 29
      frontend/app_flowy/lib/plugins/grid/application/filter/select_option_filter_bloc.dart
  6. 9 17
      frontend/app_flowy/lib/plugins/grid/application/filter/select_option_filter_list_bloc.dart
  7. 4 4
      frontend/app_flowy/lib/plugins/grid/application/filter/text_filter_editor_bloc.dart
  8. 1 1
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/choicechip/checkbox.dart
  9. 2 2
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/choicechip/choicechip.dart
  10. 2 2
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/choicechip/select_option/condition_list.dart
  11. 27 10
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/choicechip/select_option/option_list.dart
  12. 16 5
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/choicechip/select_option/select_option.dart
  13. 49 0
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/choicechip/select_option/select_option_loader.dart
  14. 1 1
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/choicechip/text.dart
  15. 4 4
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/filter_info.dart
  16. 1 1
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/menu_item.dart

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

@@ -529,7 +529,7 @@ class FieldInfo {
 
     switch (_field.fieldType) {
       case FieldType.Checkbox:
-      // case FieldType.MultiSelect:
+      case FieldType.MultiSelect:
       case FieldType.RichText:
       case FieldType.SingleSelect:
         return true;

+ 4 - 3
frontend/app_flowy/lib/plugins/grid/application/field/type_option/type_option_context.dart

@@ -109,15 +109,16 @@ class TypeOptionContext<T extends GeneratedMessage> {
 
   String get fieldId => _dataController.field.id;
 
-  Future<void> loadTypeOptionData({
-    required void Function(T) onCompleted,
+  Future<T> loadTypeOptionData({
+    void Function(T)? onCompleted,
     required void Function(FlowyError) onError,
   }) async {
     await _dataController.loadTypeOptionData().then((result) {
       result.fold((l) => null, (err) => onError(err));
     });
 
-    onCompleted(typeOption);
+    onCompleted?.call(typeOption);
+    return typeOption;
   }
 
   T get typeOption {

+ 2 - 2
frontend/app_flowy/lib/plugins/grid/application/field/type_option/type_option_data_controller.dart

@@ -33,14 +33,14 @@ class TypeOptionDataController {
     }
   }
 
-  Future<Either<Unit, FlowyError>> loadTypeOptionData() async {
+  Future<Either<TypeOptionPB, FlowyError>> loadTypeOptionData() async {
     final result = await loader.load();
     return result.fold(
       (data) {
         data.freeze();
         _data = data;
         _fieldNotifier.value = data.field_2;
-        return left(unit);
+        return left(data);
       },
       (err) {
         Log.error(err);

+ 3 - 3
frontend/app_flowy/lib/plugins/grid/application/filter/checkbox_filter_editor_bloc.dart

@@ -31,15 +31,15 @@ class CheckboxFilterEditorBloc
           updateCondition: (CheckboxFilterCondition condition) {
             _ffiService.insertCheckboxFilter(
               filterId: filterInfo.filter.id,
-              fieldId: filterInfo.field.id,
+              fieldId: filterInfo.fieldInfo.id,
               condition: condition,
             );
           },
           delete: () {
             _ffiService.deleteFilter(
-              fieldId: filterInfo.field.id,
+              fieldId: filterInfo.fieldInfo.id,
               filterId: filterInfo.filter.id,
-              fieldType: filterInfo.field.fieldType,
+              fieldType: filterInfo.fieldInfo.fieldType,
             );
           },
           didReceiveFilter: (FilterPB filter) {

+ 21 - 29
frontend/app_flowy/lib/plugins/grid/application/filter/select_option_filter_bloc.dart

@@ -1,7 +1,5 @@
-import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
+import 'package:app_flowy/plugins/grid/presentation/widgets/filter/choicechip/select_option/select_option_loader.dart';
 import 'package:app_flowy/plugins/grid/presentation/widgets/filter/filter_info.dart';
-import 'package:app_flowy/plugins/grid/presentation/widgets/header/type_option/builder.dart';
-import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/select_option_filter.pbserver.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/util.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
@@ -17,18 +15,16 @@ class SelectOptionFilterEditorBloc
   final FilterInfo filterInfo;
   final FilterFFIService _ffiService;
   final FilterListener _listener;
-  final SingleSelectTypeOptionContext typeOptionContext;
+  final SelectOptionFilterDelegate delegate;
 
-  SelectOptionFilterEditorBloc({required this.filterInfo})
-      : _ffiService = FilterFFIService(viewId: filterInfo.viewId),
+  SelectOptionFilterEditorBloc({
+    required this.filterInfo,
+    required this.delegate,
+  })  : _ffiService = FilterFFIService(viewId: filterInfo.viewId),
         _listener = FilterListener(
           viewId: filterInfo.viewId,
           filterId: filterInfo.filter.id,
         ),
-        typeOptionContext = makeSingleSelectTypeOptionContext(
-          gridId: filterInfo.viewId,
-          fieldPB: filterInfo.field.field,
-        ),
         super(SelectOptionFilterEditorState.initial(filterInfo)) {
     on<SelectOptionFilterEditorEvent>(
       (event, emit) async {
@@ -40,26 +36,26 @@ class SelectOptionFilterEditorBloc
           updateCondition: (SelectOptionCondition condition) {
             _ffiService.insertSelectOptionFilter(
               filterId: filterInfo.filter.id,
-              fieldId: filterInfo.field.id,
+              fieldId: filterInfo.fieldInfo.id,
               condition: condition,
               optionIds: state.filter.optionIds,
-              fieldType: state.filterInfo.field.fieldType,
+              fieldType: state.filterInfo.fieldInfo.fieldType,
             );
           },
           updateContent: (List<String> optionIds) {
             _ffiService.insertSelectOptionFilter(
               filterId: filterInfo.filter.id,
-              fieldId: filterInfo.field.id,
+              fieldId: filterInfo.fieldInfo.id,
               condition: state.filter.condition,
               optionIds: optionIds,
-              fieldType: state.filterInfo.field.fieldType,
+              fieldType: state.filterInfo.fieldInfo.fieldType,
             );
           },
           delete: () {
             _ffiService.deleteFilter(
-              fieldId: filterInfo.field.id,
+              fieldId: filterInfo.fieldInfo.id,
               filterId: filterInfo.filter.id,
-              fieldType: filterInfo.field.fieldType,
+              fieldType: filterInfo.fieldInfo.fieldType,
             );
           },
           didReceiveFilter: (FilterPB filter) {
@@ -92,21 +88,17 @@ class SelectOptionFilterEditorBloc
   }
 
   void _loadOptions() {
-    typeOptionContext.loadTypeOptionData(
-      onCompleted: (value) {
-        if (!isClosed) {
-          String filterDesc = '';
-          for (final option in value.options) {
-            if (state.filter.optionIds.contains(option.id)) {
-              filterDesc += "${option.name} ";
-            }
+    delegate.loadOptions().then((options) {
+      if (!isClosed) {
+        String filterDesc = '';
+        for (final option in options) {
+          if (state.filter.optionIds.contains(option.id)) {
+            filterDesc += "${option.name} ";
           }
-          add(SelectOptionFilterEditorEvent.updateFilterDescription(
-              filterDesc));
         }
-      },
-      onError: (error) => Log.error(error),
-    );
+        add(SelectOptionFilterEditorEvent.updateFilterDescription(filterDesc));
+      }
+    });
   }
 
   @override

+ 9 - 17
frontend/app_flowy/lib/plugins/grid/application/filter/select_option_filter_list_bloc.dart

@@ -1,8 +1,6 @@
 import 'dart:async';
 
-import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
-import 'package:app_flowy/plugins/grid/presentation/widgets/header/type_option/builder.dart';
-import 'package:flowy_sdk/log.dart';
+import 'package:app_flowy/plugins/grid/presentation/widgets/filter/choicechip/select_option/select_option_loader.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
@@ -12,16 +10,13 @@ part 'select_option_filter_list_bloc.freezed.dart';
 
 class SelectOptionFilterListBloc<T>
     extends Bloc<SelectOptionFilterListEvent, SelectOptionFilterListState> {
-  final SingleSelectTypeOptionContext typeOptionContext;
+  final SelectOptionFilterDelegate delegate;
   SelectOptionFilterListBloc({
     required String viewId,
     required FieldPB fieldPB,
+    required this.delegate,
     required List<String> selectedOptionIds,
-  })  : typeOptionContext = makeSingleSelectTypeOptionContext(
-          gridId: viewId,
-          fieldPB: fieldPB,
-        ),
-        super(SelectOptionFilterListState.initial(selectedOptionIds)) {
+  }) : super(SelectOptionFilterListState.initial(selectedOptionIds)) {
     on<SelectOptionFilterListEvent>(
       (event, emit) async {
         await event.when(
@@ -103,14 +98,11 @@ class SelectOptionFilterListBloc<T>
   }
 
   void _loadOptions() {
-    typeOptionContext.loadTypeOptionData(
-      onCompleted: (value) {
-        if (!isClosed) {
-          add(SelectOptionFilterListEvent.didReceiveOptions(value.options));
-        }
-      },
-      onError: (error) => Log.error(error),
-    );
+    delegate.loadOptions().then((options) {
+      if (!isClosed) {
+        add(SelectOptionFilterListEvent.didReceiveOptions(options));
+      }
+    });
   }
 
   void _startListening() {}

+ 4 - 4
frontend/app_flowy/lib/plugins/grid/application/filter/text_filter_editor_bloc.dart

@@ -31,7 +31,7 @@ class TextFilterEditorBloc
           updateCondition: (TextFilterCondition condition) {
             _ffiService.insertTextFilter(
               filterId: filterInfo.filter.id,
-              fieldId: filterInfo.field.id,
+              fieldId: filterInfo.fieldInfo.id,
               condition: condition,
               content: state.filter.content,
             );
@@ -39,16 +39,16 @@ class TextFilterEditorBloc
           updateContent: (content) {
             _ffiService.insertTextFilter(
               filterId: filterInfo.filter.id,
-              fieldId: filterInfo.field.id,
+              fieldId: filterInfo.fieldInfo.id,
               condition: state.filter.condition,
               content: content,
             );
           },
           delete: () {
             _ffiService.deleteFilter(
-              fieldId: filterInfo.field.id,
+              fieldId: filterInfo.fieldInfo.id,
               filterId: filterInfo.filter.id,
-              fieldType: filterInfo.field.fieldType,
+              fieldType: filterInfo.fieldInfo.fieldType,
             );
           },
           didReceiveFilter: (FilterPB filter) {

+ 1 - 1
frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/choicechip/checkbox.dart

@@ -107,7 +107,7 @@ class _CheckboxFilterEditorState extends State<CheckboxFilterEditor> {
       height: 20,
       child: Row(
         children: [
-          FlowyText(state.filterInfo.field.name),
+          FlowyText(state.filterInfo.fieldInfo.name),
           const HSpace(4),
           CheckboxFilterConditionList(
             filterInfo: state.filterInfo,

+ 2 - 2
frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/choicechip/choicechip.dart

@@ -37,11 +37,11 @@ class ChoiceChipButton extends StatelessWidget {
       child: FlowyButton(
         decoration: decoration,
         useIntrinsicWidth: true,
-        text: FlowyText(filterInfo.field.name, fontSize: 12),
+        text: FlowyText(filterInfo.fieldInfo.name, fontSize: 12),
         margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
         radius: const BorderRadius.all(Radius.circular(14)),
         leftIcon: svgWidget(
-          filterInfo.field.fieldType.iconName(),
+          filterInfo.fieldInfo.fieldType.iconName(),
           color: Theme.of(context).colorScheme.onSurface,
         ),
         rightIcon: _ChoicechipFilterDesc(filterDesc: filterDesc),

+ 2 - 2
frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/choicechip/select_option/condition_list.dart

@@ -32,7 +32,7 @@ class SelectOptionFilterConditionList extends StatelessWidget {
             (action) => ConditionWrapper(
               action,
               selectOptionFilter.condition == action,
-              filterInfo.field.fieldType,
+              filterInfo.fieldInfo.fieldType,
             ),
           )
           .toList(),
@@ -50,7 +50,7 @@ class SelectOptionFilterConditionList extends StatelessWidget {
   }
 
   String filterName(SelectOptionFilterPB filter) {
-    if (filterInfo.field.fieldType == FieldType.SingleSelect) {
+    if (filterInfo.fieldInfo.fieldType == FieldType.SingleSelect) {
       return filter.condition.singleSelectFilterName;
     } else {
       return filter.condition.multiSelectFilterName;

+ 27 - 10
frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/choicechip/select_option/option_list.dart

@@ -1,22 +1,23 @@
-import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
 import 'package:app_flowy/plugins/grid/application/filter/select_option_filter_list_bloc.dart';
 import 'package:app_flowy/plugins/grid/presentation/layout/sizes.dart';
 import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/extension.dart';
+import 'package:app_flowy/plugins/grid/presentation/widgets/filter/filter_info.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pbenum.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
+import 'select_option_loader.dart';
+
 class SelectOptionFilterList extends StatelessWidget {
-  final String viewId;
-  final FieldInfo fieldInfo;
+  final FilterInfo filterInfo;
   final List<String> selectedOptionIds;
   final Function(List<String>) onSelectedOptions;
   const SelectOptionFilterList({
-    required this.viewId,
-    required this.fieldInfo,
+    required this.filterInfo,
     required this.selectedOptionIds,
     required this.onSelectedOptions,
     Key? key,
@@ -25,11 +26,27 @@ class SelectOptionFilterList extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return BlocProvider(
-      create: (context) => SelectOptionFilterListBloc(
-        viewId: viewId,
-        fieldPB: fieldInfo.field,
-        selectedOptionIds: selectedOptionIds,
-      )..add(const SelectOptionFilterListEvent.initial()),
+      create: (context) {
+        late SelectOptionFilterListBloc bloc;
+        if (filterInfo.fieldInfo.fieldType == FieldType.SingleSelect) {
+          bloc = SelectOptionFilterListBloc(
+            viewId: filterInfo.viewId,
+            fieldPB: filterInfo.fieldInfo.field,
+            selectedOptionIds: selectedOptionIds,
+            delegate: SingleSelectOptionFilterDelegateImpl(filterInfo),
+          );
+        } else {
+          bloc = SelectOptionFilterListBloc(
+            viewId: filterInfo.viewId,
+            fieldPB: filterInfo.fieldInfo.field,
+            selectedOptionIds: selectedOptionIds,
+            delegate: MultiSelectOptionFilterDelegateImpl(filterInfo),
+          );
+        }
+
+        bloc.add(const SelectOptionFilterListEvent.initial());
+        return bloc;
+      },
       child:
           BlocListener<SelectOptionFilterListBloc, SelectOptionFilterListState>(
         listenWhen: (previous, current) =>

+ 16 - 5
frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/choicechip/select_option/select_option.dart

@@ -6,6 +6,7 @@ import 'package:flowy_infra_ui/flowy_infra_ui.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/field_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/select_option_filter.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
@@ -13,6 +14,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
 import '../choicechip.dart';
 import 'condition_list.dart';
 import 'option_list.dart';
+import 'select_option_loader.dart';
 
 class SelectOptionFilterChoicechip extends StatefulWidget {
   final FilterInfo filterInfo;
@@ -30,8 +32,18 @@ class _SelectOptionFilterChoicechipState
 
   @override
   void initState() {
-    bloc = SelectOptionFilterEditorBloc(filterInfo: widget.filterInfo)
-      ..add(const SelectOptionFilterEditorEvent.initial());
+    if (widget.filterInfo.fieldInfo.fieldType == FieldType.SingleSelect) {
+      bloc = SelectOptionFilterEditorBloc(
+        filterInfo: widget.filterInfo,
+        delegate: SingleSelectOptionFilterDelegateImpl(widget.filterInfo),
+      );
+    } else {
+      bloc = SelectOptionFilterEditorBloc(
+        filterInfo: widget.filterInfo,
+        delegate: MultiSelectOptionFilterDelegateImpl(widget.filterInfo),
+      );
+    }
+    bloc.add(const SelectOptionFilterEditorEvent.initial());
     super.initState();
   }
 
@@ -97,8 +109,7 @@ class _SelectOptionFilterEditorState extends State<SelectOptionFilterEditor> {
             slivers.add(
               SliverToBoxAdapter(
                 child: SelectOptionFilterList(
-                  viewId: state.filterInfo.viewId,
-                  fieldInfo: state.filterInfo.field,
+                  filterInfo: state.filterInfo,
                   selectedOptionIds: state.filter.optionIds,
                   onSelectedOptions: (optionIds) {
                     context.read<SelectOptionFilterEditorBloc>().add(
@@ -129,7 +140,7 @@ class _SelectOptionFilterEditorState extends State<SelectOptionFilterEditor> {
       height: 20,
       child: Row(
         children: [
-          FlowyText(state.filterInfo.field.name),
+          FlowyText(state.filterInfo.fieldInfo.name),
           const HSpace(4),
           SelectOptionFilterConditionList(
             filterInfo: state.filterInfo,

+ 49 - 0
frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/choicechip/select_option/select_option_loader.dart

@@ -0,0 +1,49 @@
+import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
+import 'package:app_flowy/plugins/grid/presentation/widgets/filter/filter_info.dart';
+import 'package:app_flowy/plugins/grid/presentation/widgets/header/type_option/builder.dart';
+import 'package:flowy_sdk/log.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
+
+abstract class SelectOptionFilterDelegate {
+  Future<List<SelectOptionPB>> loadOptions();
+}
+
+class SingleSelectOptionFilterDelegateImpl
+    implements SelectOptionFilterDelegate {
+  final SingleSelectTypeOptionContext typeOptionContext;
+
+  SingleSelectOptionFilterDelegateImpl(FilterInfo filterInfo)
+      : typeOptionContext = makeSingleSelectTypeOptionContext(
+          gridId: filterInfo.viewId,
+          fieldPB: filterInfo.fieldInfo.field,
+        );
+
+  @override
+  Future<List<SelectOptionPB>> loadOptions() {
+    return typeOptionContext
+        .loadTypeOptionData(
+          onError: (error) => Log.error(error),
+        )
+        .then((value) => value.options);
+  }
+}
+
+class MultiSelectOptionFilterDelegateImpl
+    implements SelectOptionFilterDelegate {
+  final MultiSelectTypeOptionContext typeOptionContext;
+
+  MultiSelectOptionFilterDelegateImpl(FilterInfo filterInfo)
+      : typeOptionContext = makeMultiSelectTypeOptionContext(
+          gridId: filterInfo.viewId,
+          fieldPB: filterInfo.fieldInfo.field,
+        );
+
+  @override
+  Future<List<SelectOptionPB>> loadOptions() {
+    return typeOptionContext
+        .loadTypeOptionData(
+          onError: (error) => Log.error(error),
+        )
+        .then((value) => value.options);
+  }
+}

+ 1 - 1
frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/choicechip/text.dart

@@ -120,7 +120,7 @@ class _TextFilterEditorState extends State<TextFilterEditor> {
       height: 20,
       child: Row(
         children: [
-          FlowyText(state.filterInfo.field.name),
+          FlowyText(state.filterInfo.fieldInfo.name),
           const HSpace(4),
           TextFilterConditionList(
             filterInfo: state.filterInfo,

+ 4 - 4
frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/filter_info.dart

@@ -9,15 +9,15 @@ import 'package:flowy_sdk/protobuf/flowy-grid/util.pb.dart';
 class FilterInfo {
   final String viewId;
   final FilterPB filter;
-  final FieldInfo field;
+  final FieldInfo fieldInfo;
 
-  FilterInfo(this.viewId, this.filter, this.field);
+  FilterInfo(this.viewId, this.filter, this.fieldInfo);
 
-  FilterInfo copyWith({FilterPB? filter, FieldInfo? field}) {
+  FilterInfo copyWith({FilterPB? filter, FieldInfo? fieldInfo}) {
     return FilterInfo(
       viewId,
       filter ?? this.filter,
-      field ?? this.field,
+      fieldInfo ?? this.fieldInfo,
     );
   }
 

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

@@ -20,7 +20,7 @@ class FilterMenuItem extends StatelessWidget {
 }
 
 Widget buildFilterChoicechip(FilterInfo filterInfo) {
-  switch (filterInfo.field.fieldType) {
+  switch (filterInfo.fieldInfo.fieldType) {
     case FieldType.Checkbox:
       return CheckboxFilterChoicechip(filterInfo: filterInfo);
     case FieldType.DateTime: