feat: support checkbox filter (#1492)

* feat: support checkbox filter

* fix: unit test

Co-authored-by: nathan <nathan@appflowy.io>
This commit is contained in:
Nathan.fooo
2022-11-27 23:44:23 +08:00
committed by GitHub
parent d6cbbf3c2f
commit c47f755155
11 changed files with 417 additions and 30 deletions

View File

@ -527,9 +527,15 @@ class FieldInfo {
bool get canCreateFilter {
if (hasFilter) return false;
if (_field.fieldType != FieldType.RichText) return false;
return true;
switch (_field.fieldType) {
case FieldType.Checkbox:
// case FieldType.MultiSelect:
case FieldType.RichText:
// case FieldType.SingleSelect:
return true;
default:
return false;
}
}
FieldInfo({required FieldPB field}) : _field = field;

View File

@ -0,0 +1,99 @@
import 'package:app_flowy/plugins/grid/presentation/widgets/filter/filter_info.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_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 'checkbox_filter_editor_bloc.freezed.dart';
class CheckboxFilterEditorBloc
extends Bloc<CheckboxFilterEditorEvent, CheckboxFilterEditorState> {
final FilterInfo filterInfo;
final FilterFFIService _ffiService;
final FilterListener _listener;
CheckboxFilterEditorBloc({required this.filterInfo})
: _ffiService = FilterFFIService(viewId: filterInfo.viewId),
_listener = FilterListener(
viewId: filterInfo.viewId,
filterId: filterInfo.filter.id,
),
super(CheckboxFilterEditorState.initial(filterInfo)) {
on<CheckboxFilterEditorEvent>(
(event, emit) async {
event.when(
initial: () async {
_startListening();
},
updateCondition: (CheckboxFilterCondition condition) {
_ffiService.insertCheckboxFilter(
filterId: filterInfo.filter.id,
fieldId: filterInfo.field.id,
condition: condition,
);
},
delete: () {
_ffiService.deleteFilter(
fieldId: filterInfo.field.id,
filterId: filterInfo.filter.id,
fieldType: filterInfo.field.fieldType,
);
},
didReceiveFilter: (FilterPB filter) {
final filterInfo = state.filterInfo.copyWith(filter: filter);
final checkboxFilter = filterInfo.checkboxFilter()!;
emit(state.copyWith(
filterInfo: filterInfo,
filter: checkboxFilter,
));
},
);
},
);
}
void _startListening() {
_listener.start(
onDeleted: () {
if (!isClosed) add(const CheckboxFilterEditorEvent.delete());
},
onUpdated: (filter) {
if (!isClosed) add(CheckboxFilterEditorEvent.didReceiveFilter(filter));
},
);
}
@override
Future<void> close() async {
await _listener.stop();
return super.close();
}
}
@freezed
class CheckboxFilterEditorEvent with _$CheckboxFilterEditorEvent {
const factory CheckboxFilterEditorEvent.initial() = _Initial;
const factory CheckboxFilterEditorEvent.didReceiveFilter(FilterPB filter) =
_DidReceiveFilter;
const factory CheckboxFilterEditorEvent.updateCondition(
CheckboxFilterCondition condition) = _UpdateCondition;
const factory CheckboxFilterEditorEvent.delete() = _Delete;
}
@freezed
class CheckboxFilterEditorState with _$CheckboxFilterEditorState {
const factory CheckboxFilterEditorState({
required FilterInfo filterInfo,
required CheckboxFilterPB filter,
}) = _GridFilterState;
factory CheckboxFilterEditorState.initial(FilterInfo filterInfo) {
return CheckboxFilterEditorState(
filterInfo: filterInfo,
filter: filterInfo.checkboxFilter()!,
);
}
}

View File

@ -1,15 +1,210 @@
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:app_flowy/plugins/grid/application/filter/checkbox_filter_editor_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/image.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';
import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_filter.pbenum.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'choicechip.dart';
class CheckboxFilterChoicechip extends StatelessWidget {
class CheckboxFilterChoicechip extends StatefulWidget {
final FilterInfo filterInfo;
const CheckboxFilterChoicechip({required this.filterInfo, Key? key})
: super(key: key);
@override
State<CheckboxFilterChoicechip> createState() =>
_CheckboxFilterChoicechipState();
}
class _CheckboxFilterChoicechipState extends State<CheckboxFilterChoicechip> {
late CheckboxFilterEditorBloc bloc;
@override
void initState() {
bloc = CheckboxFilterEditorBloc(filterInfo: widget.filterInfo)
..add(const CheckboxFilterEditorEvent.initial());
super.initState();
}
@override
void dispose() {
bloc.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ChoiceChipButton(filterInfo: filterInfo);
return BlocProvider.value(
value: bloc,
child: BlocBuilder<CheckboxFilterEditorBloc, CheckboxFilterEditorState>(
builder: (blocContext, state) {
return AppFlowyPopover(
controller: PopoverController(),
constraints: BoxConstraints.loose(const Size(200, 76)),
direction: PopoverDirection.bottomWithCenterAligned,
popupBuilder: (BuildContext context) {
return CheckboxFilterEditor(bloc: bloc);
},
child: ChoiceChipButton(
filterInfo: widget.filterInfo,
filterDesc: _makeFilterDesc(state),
),
);
},
),
);
}
String _makeFilterDesc(CheckboxFilterEditorState state) {
final prefix = LocaleKeys.grid_checkboxFilter_choicechipPrefix_is.tr();
return "$prefix ${state.filter.condition.filterName}";
}
}
class CheckboxFilterEditor extends StatefulWidget {
final CheckboxFilterEditorBloc bloc;
const CheckboxFilterEditor({required this.bloc, Key? key}) : super(key: key);
@override
State<CheckboxFilterEditor> createState() => _CheckboxFilterEditorState();
}
class _CheckboxFilterEditorState extends State<CheckboxFilterEditor> {
final popoverMutex = PopoverMutex();
@override
Widget build(BuildContext context) {
return BlocProvider.value(
value: widget.bloc,
child: BlocBuilder<CheckboxFilterEditorBloc, CheckboxFilterEditorState>(
builder: (context, state) {
final List<Widget> children = [
_buildFilterPannel(context, state),
];
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
child: IntrinsicHeight(child: Column(children: children)),
);
},
),
);
}
Widget _buildFilterPannel(
BuildContext context, CheckboxFilterEditorState state) {
return SizedBox(
height: 20,
child: Row(
children: [
FlowyText(state.filterInfo.field.name),
const HSpace(4),
CheckboxFilterConditionList(
filterInfo: state.filterInfo,
popoverMutex: popoverMutex,
onCondition: (condition) {
context
.read<CheckboxFilterEditorBloc>()
.add(CheckboxFilterEditorEvent.updateCondition(condition));
},
),
const Spacer(),
DisclosureButton(
popoverMutex: popoverMutex,
onAction: (action) {
switch (action) {
case FilterDisclosureAction.delete:
context
.read<CheckboxFilterEditorBloc>()
.add(const CheckboxFilterEditorEvent.delete());
break;
}
},
),
],
),
);
}
}
class CheckboxFilterConditionList extends StatelessWidget {
final FilterInfo filterInfo;
final PopoverMutex popoverMutex;
final Function(CheckboxFilterCondition) onCondition;
const CheckboxFilterConditionList({
required this.filterInfo,
required this.popoverMutex,
required this.onCondition,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final checkboxFilter = filterInfo.checkboxFilter()!;
return PopoverActionList<ConditionWrapper>(
asBarrier: true,
mutex: popoverMutex,
direction: PopoverDirection.bottomWithCenterAligned,
actions: CheckboxFilterCondition.values
.map(
(action) => ConditionWrapper(
action,
checkboxFilter.condition == action,
),
)
.toList(),
buildChild: (controller) {
return ConditionButton(
conditionName: checkboxFilter.condition.filterName,
onTap: () => controller.show(),
);
},
onSelected: (action, controller) async {
onCondition(action.inner);
controller.close();
},
);
}
}
class ConditionWrapper extends ActionCell {
final CheckboxFilterCondition inner;
final bool isSelected;
ConditionWrapper(this.inner, this.isSelected);
@override
Widget? rightIcon(Color iconColor) {
if (isSelected) {
return svgWidget("grid/checkmark");
} else {
return null;
}
}
@override
String get name => inner.filterName;
}
extension TextFilterConditionExtension on CheckboxFilterCondition {
String get filterName {
switch (this) {
case CheckboxFilterCondition.IsChecked:
return LocaleKeys.grid_checkboxFilter_isChecked.tr();
case CheckboxFilterCondition.IsUnChecked:
return LocaleKeys.grid_checkboxFilter_isUnchecked.tr();
default:
return "";
}
}
}

View File

@ -1,4 +1,5 @@
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/date_filter.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/text_filter.pb.dart';
@ -32,4 +33,11 @@ class FilterInfo {
}
return TextFilterPB.fromBuffer(filter.data);
}
CheckboxFilterPB? checkboxFilter() {
if (filter.fieldType != FieldType.Checkbox) {
return null;
}
return CheckboxFilterPB.fromBuffer(filter.data);
}
}