Merge pull request #1456 from AppFlowy-IO/filter_bloc

Add Filter bloc
This commit is contained in:
Nathan.fooo
2022-11-16 08:50:03 +08:00
committed by GitHub
34 changed files with 799 additions and 124 deletions

View File

@ -0,0 +1,206 @@
import 'package:app_flowy/plugins/grid/application/filter/filter_listener.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_filter.pbenum.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';
import 'package:flowy_sdk/protobuf/flowy-grid/text_filter.pbserver.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_service.dart';
part 'filter_bloc.freezed.dart';
class GridFilterBloc extends Bloc<GridFilterEvent, GridFilterState> {
final String viewId;
final FilterFFIService _ffiService;
final FilterListener _listener;
GridFilterBloc({required this.viewId})
: _ffiService = FilterFFIService(viewId: viewId),
_listener = FilterListener(viewId: viewId),
super(GridFilterState.initial()) {
on<GridFilterEvent>(
(event, emit) async {
event.when(
initial: () async {
_startListening();
await _loadFilters();
},
deleteFilter: (
String fieldId,
String filterId,
FieldType fieldType,
) {
_ffiService.deleteFilter(
fieldId: fieldId,
filterId: filterId,
fieldType: fieldType,
);
},
didReceiveFilters: (filters) {
emit(state.copyWith(filters: filters));
},
createCheckboxFilter: (
String fieldId,
CheckboxFilterCondition condition,
) {
_ffiService.createCheckboxFilter(
fieldId: fieldId,
condition: condition,
);
},
createNumberFilter: (
String fieldId,
NumberFilterCondition condition,
String content,
) {
_ffiService.createNumberFilter(
fieldId: fieldId,
condition: condition,
content: content,
);
},
createTextFilter: (
String fieldId,
TextFilterCondition condition,
String content,
) {
_ffiService.createTextFilter(
fieldId: fieldId,
condition: condition,
);
},
createDateFilter: (
String fieldId,
DateFilterCondition condition,
int timestamp,
) {
_ffiService.createDateFilter(
fieldId: fieldId,
condition: condition,
timestamp: timestamp,
);
},
createDateFilterInRange: (
String fieldId,
DateFilterCondition condition,
int start,
int end,
) {
_ffiService.createDateFilter(
fieldId: fieldId,
condition: condition,
start: start,
end: end,
);
},
);
},
);
}
void _startListening() {
_listener.start(onFilterChanged: (result) {
result.fold(
(changeset) {
final List<FilterPB> filters = List.from(state.filters);
// Deletes the filters
final deleteFilterIds =
changeset.deleteFilters.map((e) => e.id).toList();
filters.retainWhere(
(element) => !deleteFilterIds.contains(element.id),
);
// Inserts the new fitler if it's not exist
for (final newFilter in changeset.insertFilters) {
final index =
filters.indexWhere((element) => element.id == newFilter.id);
if (index == -1) {
filters.add(newFilter);
}
}
if (!isClosed) {
add(GridFilterEvent.didReceiveFilters(filters));
}
},
(err) => Log.error(err),
);
});
}
Future<void> _loadFilters() async {
final result = await _ffiService.getAllFilters();
result.fold(
(filters) {
if (!isClosed) {
add(GridFilterEvent.didReceiveFilters(filters));
}
},
(err) => Log.error(err),
);
}
@override
Future<void> close() async {
await _listener.stop();
return super.close();
}
}
@freezed
class GridFilterEvent with _$GridFilterEvent {
const factory GridFilterEvent.initial() = _Initial;
const factory GridFilterEvent.didReceiveFilters(List<FilterPB> filters) =
_DidReceiveFilters;
const factory GridFilterEvent.deleteFilter({
required String fieldId,
required String filterId,
required FieldType fieldType,
}) = _DeleteFilter;
const factory GridFilterEvent.createTextFilter({
required String fieldId,
required TextFilterCondition condition,
required String content,
}) = _CreateTextFilter;
const factory GridFilterEvent.createCheckboxFilter({
required String fieldId,
required CheckboxFilterCondition condition,
}) = _CreateCheckboxFilter;
const factory GridFilterEvent.createNumberFilter({
required String fieldId,
required NumberFilterCondition condition,
required String content,
}) = _CreateCheckboxFitler;
const factory GridFilterEvent.createDateFilter({
required String fieldId,
required DateFilterCondition condition,
required int start,
}) = _CreateDateFitler;
const factory GridFilterEvent.createDateFilterInRange({
required String fieldId,
required DateFilterCondition condition,
required int start,
required int end,
}) = _CreateDateFitlerInRange;
}
@freezed
class GridFilterState with _$GridFilterState {
const factory GridFilterState({
required List<FilterPB> filters,
}) = _GridFilterState;
factory GridFilterState.initial() => const GridFilterState(
filters: [],
);
}

View File

@ -0,0 +1,53 @@
import 'dart:typed_data';
import 'package:app_flowy/core/grid_notification.dart';
import 'package:flowy_infra/notifier.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/filter_changeset.pb.dart';
import 'package:dartz/dartz.dart';
typedef UpdateFilterNotifiedValue
= Either<FilterChangesetNotificationPB, FlowyError>;
class FilterListener {
final String viewId;
PublishNotifier<UpdateFilterNotifiedValue>? _filterNotifier =
PublishNotifier();
GridNotificationListener? _listener;
FilterListener({required this.viewId});
void start({
required void Function(UpdateFilterNotifiedValue) onFilterChanged,
}) {
_filterNotifier?.addPublishListener(onFilterChanged);
_listener = GridNotificationListener(
objectId: viewId,
handler: _handler,
);
}
void _handler(
GridNotification ty,
Either<Uint8List, FlowyError> result,
) {
switch (ty) {
case GridNotification.DidUpdateFilter:
result.fold(
(payload) => _filterNotifier?.value =
left(FilterChangesetNotificationPB.fromBuffer(payload)),
(error) => _filterNotifier?.value = right(error),
);
break;
default:
break;
}
}
Future<void> stop() async {
await _listener?.stop();
_filterNotifier?.dispose();
_filterNotifier = null;
}
}

View File

@ -0,0 +1,202 @@
import 'package:dartz/dartz.dart';
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/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';
import 'package:flowy_sdk/protobuf/flowy-grid/number_filter.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/select_option_filter.pbserver.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/setting_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/text_filter.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/util.pb.dart';
import 'package:fixnum/fixnum.dart' as $fixnum;
class FilterFFIService {
final String viewId;
const FilterFFIService({required this.viewId});
Future<Either<List<FilterPB>, FlowyError>> getAllFilters() {
final payload = GridIdPB()..value = viewId;
return GridEventGetAllFilters(payload).send().then((result) {
return result.fold(
(repeated) => left(repeated.items),
(r) => right(r),
);
});
}
Future<Either<Unit, FlowyError>> createTextFilter({
required String fieldId,
required TextFilterCondition condition,
String content = "",
}) {
final filter = TextFilterPB()
..condition = condition
..content = content;
return createFilter(
fieldId: fieldId,
fieldType: FieldType.RichText,
data: filter.writeToBuffer(),
);
}
Future<Either<Unit, FlowyError>> createCheckboxFilter({
required String fieldId,
required CheckboxFilterCondition condition,
}) {
final filter = CheckboxFilterPB()..condition = condition;
return createFilter(
fieldId: fieldId,
fieldType: FieldType.Checkbox,
data: filter.writeToBuffer(),
);
}
Future<Either<Unit, FlowyError>> createNumberFilter({
required String fieldId,
required NumberFilterCondition condition,
String content = "",
}) {
final filter = NumberFilterPB()
..condition = condition
..content = content;
return createFilter(
fieldId: fieldId,
fieldType: FieldType.Number,
data: filter.writeToBuffer(),
);
}
Future<Either<Unit, FlowyError>> createDateFilter({
required String fieldId,
required DateFilterCondition condition,
int? start,
int? end,
int? timestamp,
}) {
var filter = DateFilterPB();
if (timestamp != null) {
filter.timestamp = $fixnum.Int64(timestamp);
} else {
if (start != null && end != null) {
filter.start = $fixnum.Int64(start);
filter.end = $fixnum.Int64(end);
} else {
throw Exception(
"Start and end should not be null if the timestamp is null");
}
}
return createFilter(
fieldId: fieldId,
fieldType: FieldType.DateTime,
data: filter.writeToBuffer(),
);
}
Future<Either<Unit, FlowyError>> createURLFilter({
required String fieldId,
required TextFilterCondition condition,
String content = "",
}) {
final filter = TextFilterPB()
..condition = condition
..content = content;
return createFilter(
fieldId: fieldId,
fieldType: FieldType.URL,
data: filter.writeToBuffer(),
);
}
Future<Either<Unit, FlowyError>> createSingleSelectFilter({
required String fieldId,
required SelectOptionCondition condition,
List<String> optionIds = const [],
}) {
final filter = SelectOptionFilterPB()
..condition = condition
..optionIds.addAll(optionIds);
return createFilter(
fieldId: fieldId,
fieldType: FieldType.SingleSelect,
data: filter.writeToBuffer(),
);
}
Future<Either<Unit, FlowyError>> createMultiSelectFilter({
required String fieldId,
required SelectOptionCondition condition,
List<String> optionIds = const [],
}) {
final filter = SelectOptionFilterPB()
..condition = condition
..optionIds.addAll(optionIds);
return createFilter(
fieldId: fieldId,
fieldType: FieldType.MultiSelect,
data: filter.writeToBuffer(),
);
}
Future<Either<Unit, FlowyError>> createFilter({
required String fieldId,
required FieldType fieldType,
required List<int> data,
}) {
TextFilterCondition.DoesNotContain.value;
final insertFilterPayload = CreateFilterPayloadPB.create()
..fieldId = fieldId
..fieldType = fieldType
..data = data;
final payload = GridSettingChangesetPB.create()
..gridId = viewId
..insertFilter = insertFilterPayload;
return GridEventUpdateGridSetting(payload).send().then((result) {
return result.fold(
(l) => left(l),
(err) {
Log.error(err);
return right(err);
},
);
});
}
Future<Either<Unit, FlowyError>> deleteFilter({
required String fieldId,
required String filterId,
required FieldType fieldType,
}) {
TextFilterCondition.DoesNotContain.value;
final deleteFilterPayload = DeleteFilterPayloadPB.create()
..fieldId = fieldId
..filterId = filterId
..fieldType = fieldType;
final payload = GridSettingChangesetPB.create()
..gridId = viewId
..deleteFilter = deleteFilterPayload;
return GridEventUpdateGridSetting(payload).send().then((result) {
return result.fold(
(l) => left(l),
(err) {
Log.error(err);
return right(err);
},
);
});
}
}