From 553cfb3f5eed9a1e50462c99fbc986a2f36f02d4 Mon Sep 17 00:00:00 2001 From: nathan Date: Wed, 30 Nov 2022 16:43:57 +0800 Subject: [PATCH] chore: support checklist filter --- .../app_flowy/assets/translations/en.json | 4 + .../application/field/field_controller.dart | 2 +- .../filter/checklist_filter_bloc.dart | 107 +++++++++++ .../select_option_filter_editor_bloc.dart | 0 .../widgets/filter/choicechip/checklist.dart | 14 -- .../choicechip/checklist/checklist.dart | 172 ++++++++++++++++++ .../widgets/filter/filter_info.dart | 9 + .../widgets/filter/menu_item.dart | 2 +- 8 files changed, 294 insertions(+), 16 deletions(-) create mode 100644 frontend/app_flowy/lib/plugins/grid/application/filter/checklist_filter_bloc.dart delete mode 100644 frontend/app_flowy/lib/plugins/grid/application/filter/select_option_filter_editor_bloc.dart delete mode 100644 frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/choicechip/checklist.dart create mode 100644 frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/choicechip/checklist/checklist.dart diff --git a/frontend/app_flowy/assets/translations/en.json b/frontend/app_flowy/assets/translations/en.json index 64f34ac1ca..c6ae3cf596 100644 --- a/frontend/app_flowy/assets/translations/en.json +++ b/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", diff --git a/frontend/app_flowy/lib/plugins/grid/application/field/field_controller.dart b/frontend/app_flowy/lib/plugins/grid/application/field/field_controller.dart index 0c520fe7d7..4119139c64 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/field/field_controller.dart +++ b/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; diff --git a/frontend/app_flowy/lib/plugins/grid/application/filter/checklist_filter_bloc.dart b/frontend/app_flowy/lib/plugins/grid/application/filter/checklist_filter_bloc.dart new file mode 100644 index 0000000000..05e801081a --- /dev/null +++ b/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 { + 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( + (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 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: '', + ); + } +} diff --git a/frontend/app_flowy/lib/plugins/grid/application/filter/select_option_filter_editor_bloc.dart b/frontend/app_flowy/lib/plugins/grid/application/filter/select_option_filter_editor_bloc.dart deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/choicechip/checklist.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/choicechip/checklist.dart deleted file mode 100644 index 641a61f009..0000000000 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/choicechip/checklist.dart +++ /dev/null @@ -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); - } -} diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/choicechip/checklist/checklist.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/choicechip/checklist/checklist.dart new file mode 100644 index 0000000000..114ececac7 --- /dev/null +++ b/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 createState() => + _ChecklistFilterChoicechipState(); +} + +class _ChecklistFilterChoicechipState extends State { + 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( + 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 { + @override + Widget build(BuildContext context) { + return BlocProvider.value( + value: widget.bloc, + child: BlocBuilder( + 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() + .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( + 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() + .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 ""; + } + } +} diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/filter_info.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/filter_info.dart index 61addc2808..5ec68512ab 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/filter_info.dart +++ b/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; + } + } } diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/menu_item.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/menu_item.dart index 062fc21cbd..3333852708 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/filter/menu_item.dart +++ b/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';