Browse Source

chore: support checklist filter

nathan 2 years ago
parent
commit
553cfb3f5e

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

@@ -197,6 +197,10 @@
         "is": "is"
       }
     },
+    "checklistFilter": {
+      "isComplete": "is complete",
+      "isIncomplted": "is incomplete"
+    },
     "singleSelectOptionFilter": {
       "is": "Is",
       "isNot": "Is not",

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

@@ -522,7 +522,7 @@ class FieldInfo {
       case FieldType.MultiSelect:
       case FieldType.RichText:
       case FieldType.SingleSelect:
-        // case FieldType.Checklist:
+      case FieldType.Checklist:
         return true;
       default:
         return false;

+ 107 - 0
frontend/app_flowy/lib/plugins/grid/application/filter/checklist_filter_bloc.dart

@@ -0,0 +1,107 @@
+import 'package:app_flowy/plugins/grid/presentation/widgets/filter/filter_info.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/checklist_filter.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/util.pb.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+import 'dart:async';
+import 'filter_listener.dart';
+import 'filter_service.dart';
+
+part 'checklist_filter_bloc.freezed.dart';
+
+class ChecklistFilterEditorBloc
+    extends Bloc<ChecklistFilterEditorEvent, ChecklistFilterEditorState> {
+  final FilterInfo filterInfo;
+  final FilterFFIService _ffiService;
+  // final SelectOptionFFIService _selectOptionService;
+  final FilterListener _listener;
+
+  ChecklistFilterEditorBloc({
+    required this.filterInfo,
+  })  : _ffiService = FilterFFIService(viewId: filterInfo.viewId),
+        // _selectOptionService =
+        //           SelectOptionFFIService(cellId: cellController.cellId)
+        _listener = FilterListener(
+          viewId: filterInfo.viewId,
+          filterId: filterInfo.filter.id,
+        ),
+        super(ChecklistFilterEditorState.initial(filterInfo)) {
+    on<ChecklistFilterEditorEvent>(
+      (event, emit) async {
+        event.when(
+          initial: () async {
+            _startListening();
+          },
+          updateCondition: (ChecklistFilterCondition condition) {
+            _ffiService.insertChecklistFilter(
+              filterId: filterInfo.filter.id,
+              fieldId: filterInfo.fieldInfo.id,
+              condition: condition,
+            );
+          },
+          delete: () {
+            _ffiService.deleteFilter(
+              fieldId: filterInfo.fieldInfo.id,
+              filterId: filterInfo.filter.id,
+              fieldType: filterInfo.fieldInfo.fieldType,
+            );
+          },
+          didReceiveFilter: (FilterPB filter) {
+            final filterInfo = state.filterInfo.copyWith(filter: filter);
+            final checklistFilter = filterInfo.checklistFilter()!;
+            emit(state.copyWith(
+              filterInfo: filterInfo,
+              filter: checklistFilter,
+            ));
+          },
+        );
+      },
+    );
+  }
+
+  void _startListening() {
+    _listener.start(
+      onDeleted: () {
+        if (!isClosed) add(const ChecklistFilterEditorEvent.delete());
+      },
+      onUpdated: (filter) {
+        if (!isClosed) {
+          add(ChecklistFilterEditorEvent.didReceiveFilter(filter));
+        }
+      },
+    );
+  }
+
+  @override
+  Future<void> close() async {
+    await _listener.stop();
+    return super.close();
+  }
+}
+
+@freezed
+class ChecklistFilterEditorEvent with _$ChecklistFilterEditorEvent {
+  const factory ChecklistFilterEditorEvent.initial() = _Initial;
+  const factory ChecklistFilterEditorEvent.didReceiveFilter(FilterPB filter) =
+      _DidReceiveFilter;
+  const factory ChecklistFilterEditorEvent.updateCondition(
+      ChecklistFilterCondition condition) = _UpdateCondition;
+  const factory ChecklistFilterEditorEvent.delete() = _Delete;
+}
+
+@freezed
+class ChecklistFilterEditorState with _$ChecklistFilterEditorState {
+  const factory ChecklistFilterEditorState({
+    required FilterInfo filterInfo,
+    required ChecklistFilterPB filter,
+    required String filterDesc,
+  }) = _GridFilterState;
+
+  factory ChecklistFilterEditorState.initial(FilterInfo filterInfo) {
+    return ChecklistFilterEditorState(
+      filterInfo: filterInfo,
+      filter: filterInfo.checklistFilter()!,
+      filterDesc: '',
+    );
+  }
+}

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


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

@@ -1,14 +0,0 @@
-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);
-  }
-}

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

@@ -0,0 +1,172 @@
+import 'package:app_flowy/generated/locale_keys.g.dart';
+import 'package:app_flowy/plugins/grid/application/filter/checklist_filter_bloc.dart';
+import 'package:app_flowy/plugins/grid/presentation/widgets/filter/condition_button.dart';
+import 'package:app_flowy/plugins/grid/presentation/widgets/filter/disclosure_button.dart';
+import 'package:app_flowy/plugins/grid/presentation/widgets/filter/filter_info.dart';
+import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
+import 'package:appflowy_popover/appflowy_popover.dart';
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flowy_infra_ui/widget/spacing.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/checklist_filter.pbenum.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import '../choicechip.dart';
+
+class ChecklistFilterChoicechip extends StatefulWidget {
+  final FilterInfo filterInfo;
+  const ChecklistFilterChoicechip({required this.filterInfo, Key? key})
+      : super(key: key);
+
+  @override
+  State<ChecklistFilterChoicechip> createState() =>
+      _ChecklistFilterChoicechipState();
+}
+
+class _ChecklistFilterChoicechipState extends State<ChecklistFilterChoicechip> {
+  late ChecklistFilterEditorBloc bloc;
+  late PopoverMutex popoverMutex;
+
+  @override
+  void initState() {
+    popoverMutex = PopoverMutex();
+    bloc = ChecklistFilterEditorBloc(filterInfo: widget.filterInfo);
+    bloc.add(const ChecklistFilterEditorEvent.initial());
+    super.initState();
+  }
+
+  @override
+  void dispose() {
+    bloc.close();
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocProvider.value(
+      value: bloc,
+      child: BlocBuilder<ChecklistFilterEditorBloc, ChecklistFilterEditorState>(
+        builder: (blocContext, state) {
+          return AppFlowyPopover(
+            controller: PopoverController(),
+            constraints: BoxConstraints.loose(const Size(200, 160)),
+            direction: PopoverDirection.bottomWithCenterAligned,
+            popupBuilder: (BuildContext context) {
+              return ChecklistFilterEditor(
+                bloc: bloc,
+                popoverMutex: popoverMutex,
+              );
+            },
+            child: ChoiceChipButton(
+              filterInfo: widget.filterInfo,
+              filterDesc: state.filterDesc,
+            ),
+          );
+        },
+      ),
+    );
+  }
+}
+
+class ChecklistFilterEditor extends StatefulWidget {
+  final ChecklistFilterEditorBloc bloc;
+  final PopoverMutex popoverMutex;
+  const ChecklistFilterEditor(
+      {required this.bloc, required this.popoverMutex, Key? key})
+      : super(key: key);
+
+  @override
+  ChecklistState createState() => ChecklistState();
+}
+
+class ChecklistState extends State<ChecklistFilterEditor> {
+  @override
+  Widget build(BuildContext context) {
+    return BlocProvider.value(
+      value: widget.bloc,
+      child: BlocBuilder<ChecklistFilterEditorBloc, ChecklistFilterEditorState>(
+        builder: (context, state) {
+          return SizedBox(
+            height: 20,
+            child: Row(
+              children: [
+                FlowyText(state.filterInfo.fieldInfo.name),
+                const HSpace(4),
+                ChecklistFilterConditionList(
+                  filterInfo: state.filterInfo,
+                ),
+                const Spacer(),
+                DisclosureButton(
+                  popoverMutex: widget.popoverMutex,
+                  onAction: (action) {
+                    switch (action) {
+                      case FilterDisclosureAction.delete:
+                        context
+                            .read<ChecklistFilterEditorBloc>()
+                            .add(const ChecklistFilterEditorEvent.delete());
+                        break;
+                    }
+                  },
+                ),
+              ],
+            ),
+          );
+        },
+      ),
+    );
+  }
+}
+
+class ChecklistFilterConditionList extends StatelessWidget {
+  final FilterInfo filterInfo;
+  const ChecklistFilterConditionList({
+    required this.filterInfo,
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final checklistFilter = filterInfo.checklistFilter()!;
+    return PopoverActionList<ConditionWrapper>(
+      asBarrier: true,
+      direction: PopoverDirection.bottomWithCenterAligned,
+      actions: ChecklistFilterCondition.values
+          .map((action) => ConditionWrapper(action))
+          .toList(),
+      buildChild: (controller) {
+        return ConditionButton(
+          conditionName: checklistFilter.condition.filterName,
+          onTap: () => controller.show(),
+        );
+      },
+      onSelected: (action, controller) async {
+        context
+            .read<ChecklistFilterEditorBloc>()
+            .add(ChecklistFilterEditorEvent.updateCondition(action.inner));
+        controller.close();
+      },
+    );
+  }
+}
+
+class ConditionWrapper extends ActionCell {
+  final ChecklistFilterCondition inner;
+
+  ConditionWrapper(this.inner);
+
+  @override
+  String get name => inner.filterName;
+}
+
+extension ChecklistFilterConditionExtension on ChecklistFilterCondition {
+  String get filterName {
+    switch (this) {
+      case ChecklistFilterCondition.IsComplete:
+        return LocaleKeys.grid_checklistFilter_isComplete.tr();
+      case ChecklistFilterCondition.IsIncomplete:
+        return LocaleKeys.grid_checklistFilter_isIncomplted.tr();
+      default:
+        return "";
+    }
+  }
+}

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

@@ -1,5 +1,6 @@
 import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_filter.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/checklist_filter.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/date_filter.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/select_option_filter.pbserver.dart';
@@ -50,4 +51,12 @@ class FilterInfo {
       return null;
     }
   }
+
+  ChecklistFilterPB? checklistFilter() {
+    if (filter.fieldType == FieldType.Checklist) {
+      return ChecklistFilterPB.fromBuffer(filter.data);
+    } else {
+      return null;
+    }
+  }
 }

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

@@ -2,7 +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/checklist/checklist.dart';
 import 'choicechip/date.dart';
 import 'choicechip/number.dart';
 import 'choicechip/select_option/select_option.dart';