test: filter integration (#2821)

* test: add checklist filter test

* fix: widget reference to invalid databaseController

* fix: SelectOptionFilterList doesn't expand to fill the CustomScrollView

* test: add single select and multi-select filter test

* ci: set protoc version
This commit is contained in:
Nathan.fooo
2023-06-16 14:32:32 +08:00
committed by GitHub
parent 63eee8b415
commit 14dee6b797
10 changed files with 239 additions and 72 deletions

View File

@ -3,6 +3,7 @@ import 'package:appflowy/plugins/database_view/application/layout/calendar_setti
import 'package:appflowy/plugins/database_view/application/view/view_cache.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/calendar_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/database_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/group.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/group_changeset.pb.dart';
@ -129,29 +130,33 @@ class DatabaseController {
Future<Either<Unit, FlowyError>> open() async {
return _databaseViewBackendSvc.openGrid().then((result) {
return result.fold(
(database) async {
(DatabasePB database) async {
databaseLayout = database.layoutType;
// Listen on layout changed if database layout is calendar
if (databaseLayout == DatabaseLayoutPB.Calendar) {
_listenOnCalendarLayoutChanged();
}
_databaseCallbacks?.onDatabaseChanged?.call(database);
_viewCache.rowCache.setInitialRows(database.rows);
return await fieldController
.loadFields(
// Load the actual database field data.
final fieldsOrFail = await fieldController.loadFields(
fieldIds: database.fields,
)
.then(
(result) {
return result.fold(
(l) => Future(() async {
await _loadGroups();
await _loadLayoutSetting();
return left(l);
}),
(err) => right(err),
);
);
return fieldsOrFail.fold(
(fields) {
// Notify the database is changed after the fields are loaded.
// The database won't can't be used until the fields are loaded.
_databaseCallbacks?.onDatabaseChanged?.call(database);
_viewCache.rowCache.setInitialRows(database.rows);
return Future(() async {
await _loadGroups();
await _loadLayoutSetting();
return left(fields);
});
},
(err) {
Log.error(err);
return right(err);
},
);
},

View File

@ -33,7 +33,8 @@ class _GridFieldNotifier extends ChangeNotifier {
notifyListeners();
}
List<FieldInfo> get fieldInfos => _fieldInfos;
UnmodifiableListView<FieldInfo> get fieldInfos =>
UnmodifiableListView(_fieldInfos);
}
class _GridFilterNotifier extends ChangeNotifier {
@ -85,9 +86,11 @@ class FieldController {
final FilterBackendService _filterBackendSvc;
final SortBackendService _sortBackendSvc;
bool _isDisposed = false;
// Field callbacks
final Map<OnReceiveFields, VoidCallback> _fieldCallbacks = {};
_GridFieldNotifier? _fieldNotifier = _GridFieldNotifier();
final _GridFieldNotifier _fieldNotifier = _GridFieldNotifier();
// Field updated callbacks
final Map<OnReceiveUpdateFields, void Function(List<FieldInfo>)>
@ -107,15 +110,15 @@ class FieldController {
final Map<String, SortPB> _sortPBByFieldId = {};
// Getters
List<FieldInfo> get fieldInfos => [..._fieldNotifier?.fieldInfos ?? []];
List<FieldInfo> get fieldInfos => [..._fieldNotifier.fieldInfos];
List<FilterInfo> get filterInfos => [..._filterNotifier?.filters ?? []];
List<SortInfo> get sortInfos => [..._sortNotifier?.sorts ?? []];
FieldInfo? getField(String fieldId) {
final fields = _fieldNotifier?.fieldInfos
.where((element) => element.id == fieldId)
.toList() ??
[];
final fields = _fieldNotifier.fieldInfos
.where((element) => element.id == fieldId)
.toList();
if (fields.isEmpty) {
return null;
}
@ -169,6 +172,10 @@ class FieldController {
_listenOnSortChanged();
_settingBackendSvc.getSetting().then((result) {
if (_isDisposed) {
return;
}
result.fold(
(setting) => _updateSetting(setting),
(err) => Log.error(err),
@ -257,6 +264,10 @@ class FieldController {
_filtersListener.start(
onFilterChanged: (result) {
if (_isDisposed) {
return;
}
result.fold(
(FilterChangesetNotificationPB changeset) {
final List<FilterInfo> filters = filterInfos;
@ -351,6 +362,9 @@ class FieldController {
_sortsListener.start(
onSortChanged: (result) {
if (_isDisposed) {
return;
}
result.fold(
(SortChangesetNotificationPB changeset) {
final List<SortInfo> newSortInfos = sortInfos;
@ -371,6 +385,10 @@ class FieldController {
//Listen on setting changes
_settingListener.start(
onSettingUpdated: (result) {
if (_isDisposed) {
return;
}
result.fold(
(setting) => _updateSetting(setting),
(r) => Log.error(r),
@ -385,6 +403,9 @@ class FieldController {
onFieldsChanged: (result) {
result.fold(
(changeset) {
if (_isDisposed) {
return;
}
_deleteFields(changeset.deletedFields);
_insertFields(changeset.insertedFields);
@ -417,27 +438,29 @@ class FieldController {
}
void _updateFieldInfos() {
if (_fieldNotifier != null) {
for (final field in _fieldNotifier!.fieldInfos) {
field._isGroupField = _groupConfigurationByFieldId[field.id] != null;
field._hasFilter = _filterPBByFieldId[field.id] != null;
field._hasSort = _sortPBByFieldId[field.id] != null;
}
_fieldNotifier?.notify();
for (final field in _fieldNotifier.fieldInfos) {
field._isGroupField = _groupConfigurationByFieldId[field.id] != null;
field._hasFilter = _filterPBByFieldId[field.id] != null;
field._hasSort = _sortPBByFieldId[field.id] != null;
}
_fieldNotifier.notify();
}
Future<void> dispose() async {
if (_isDisposed) {
Log.warn('FieldController is already disposed');
return;
}
_isDisposed = true;
await _fieldListener.stop();
await _filtersListener.stop();
await _settingListener.stop();
await _sortsListener.stop();
for (final callback in _fieldCallbacks.values) {
_fieldNotifier?.removeListener(callback);
_fieldNotifier.removeListener(callback);
}
_fieldNotifier?.dispose();
_fieldNotifier = null;
_fieldNotifier.dispose();
for (final callback in _filterCallbacks.values) {
_filterNotifier?.removeListener(callback);
@ -460,7 +483,11 @@ class FieldController {
return Future(
() => result.fold(
(newFields) {
_fieldNotifier?.fieldInfos =
if (_isDisposed) {
return left(unit);
}
_fieldNotifier.fieldInfos =
newFields.map((field) => FieldInfo(field: field)).toList();
_loadFilters();
_loadSorts();
@ -551,7 +578,7 @@ class FieldController {
}
_fieldCallbacks[onReceiveFields] = callback;
_fieldNotifier?.addListener(callback);
_fieldNotifier.addListener(callback);
}
if (onFilters != null) {
@ -588,7 +615,7 @@ class FieldController {
if (onFieldsListener != null) {
final callback = _fieldCallbacks.remove(onFieldsListener);
if (callback != null) {
_fieldNotifier?.removeListener(callback);
_fieldNotifier.removeListener(callback);
}
}
if (onFiltersListener != null) {
@ -616,7 +643,7 @@ class FieldController {
};
newFields.retainWhere((field) => (deletedFieldMap[field.id] == null));
_fieldNotifier?.fieldInfos = newFields;
_fieldNotifier.fieldInfos = newFields;
}
void _insertFields(List<IndexFieldPB> insertedFields) {
@ -632,7 +659,7 @@ class FieldController {
newFieldInfos.add(fieldInfo);
}
}
_fieldNotifier?.fieldInfos = newFieldInfos;
_fieldNotifier.fieldInfos = newFieldInfos;
}
List<FieldInfo> _updateFields(List<FieldPB> updatedFieldPBs) {
@ -654,7 +681,7 @@ class FieldController {
}
if (updatedFields.isNotEmpty) {
_fieldNotifier?.fieldInfos = newFields;
_fieldNotifier.fieldInfos = newFields;
}
return updatedFields;
}

View File

@ -29,9 +29,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
String get viewId => databaseController.viewId;
BoardBloc({required ViewPB view})
: databaseController = DatabaseController(
view: view,
),
: databaseController = DatabaseController(view: view),
super(BoardState.initial(view.id)) {
boardController = AppFlowyBoardController(
onMoveGroup: (

View File

@ -32,15 +32,13 @@ import 'widgets/shortcuts.dart';
import 'widgets/toolbar/grid_toolbar.dart';
class GridPage extends StatefulWidget {
GridPage({
const GridPage({
required this.view,
this.onDeleted,
Key? key,
}) : databaseController = DatabaseController(view: view),
super(key: key);
}) : super(key: key);
final ViewPB view;
final DatabaseController databaseController;
final VoidCallback? onDeleted;
@override
@ -48,6 +46,14 @@ class GridPage extends StatefulWidget {
}
class _GridPageState extends State<GridPage> {
late DatabaseController databaseController;
@override
void initState() {
super.initState();
databaseController = DatabaseController(view: widget.view);
}
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
@ -55,19 +61,19 @@ class _GridPageState extends State<GridPage> {
BlocProvider<GridBloc>(
create: (context) => GridBloc(
view: widget.view,
databaseController: widget.databaseController,
databaseController: databaseController,
)..add(const GridEvent.initial()),
),
BlocProvider<GridFilterMenuBloc>(
create: (context) => GridFilterMenuBloc(
viewId: widget.view.id,
fieldController: widget.databaseController.fieldController,
fieldController: databaseController.fieldController,
)..add(const GridFilterMenuEvent.initial()),
),
BlocProvider<SortMenuBloc>(
create: (context) => SortMenuBloc(
viewId: widget.view.id,
fieldController: widget.databaseController.fieldController,
fieldController: databaseController.fieldController,
)..add(const SortMenuEvent.initial()),
),
BlocProvider<DatabaseSettingBloc>(

View File

@ -93,7 +93,7 @@ class ChecklistState extends State<ChecklistFilterEditor> {
children: [
FlowyText(state.filterInfo.fieldInfo.name),
const HSpace(4),
ChecklistFilterConditionPBList(
ChecklistFilterConditionList(
filterInfo: state.filterInfo,
),
const Spacer(),
@ -118,9 +118,9 @@ class ChecklistState extends State<ChecklistFilterEditor> {
}
}
class ChecklistFilterConditionPBList extends StatelessWidget {
class ChecklistFilterConditionList extends StatelessWidget {
final FilterInfo filterInfo;
const ChecklistFilterConditionPBList({
const ChecklistFilterConditionList({
required this.filterInfo,
Key? key,
}) : super(key: key);

View File

@ -2,7 +2,6 @@ import 'package:appflowy/plugins/database_view/grid/application/filter/select_op
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.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:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
import 'package:flutter/material.dart';
@ -58,16 +57,16 @@ class SelectOptionFilterList extends StatelessWidget {
SelectOptionFilterListState>(
builder: (context, state) {
return ListView.separated(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
controller: ScrollController(),
itemCount: state.visibleOptions.length,
separatorBuilder: (context, index) {
return VSpace(GridSize.typeOptionSeparatorHeight);
},
physics: StyledScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
final option = state.visibleOptions[index];
return _SelectOptionFilterCell(
return SelectOptionFilterCell(
option: option.optionPB,
isSelected: option.isSelected,
);
@ -80,21 +79,20 @@ class SelectOptionFilterList extends StatelessWidget {
}
}
class _SelectOptionFilterCell extends StatefulWidget {
class SelectOptionFilterCell extends StatefulWidget {
final SelectOptionPB option;
final bool isSelected;
const _SelectOptionFilterCell({
const SelectOptionFilterCell({
required this.option,
required this.isSelected,
Key? key,
}) : super(key: key);
@override
State<_SelectOptionFilterCell> createState() =>
_SelectOptionFilterCellState();
State<SelectOptionFilterCell> createState() => _SelectOptionFilterCellState();
}
class _SelectOptionFilterCellState extends State<_SelectOptionFilterCell> {
class _SelectOptionFilterCellState extends State<SelectOptionFilterCell> {
@override
Widget build(BuildContext context) {
return SizedBox(