mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
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:
@ -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);
|
||||
},
|
||||
);
|
||||
},
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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: (
|
||||
|
@ -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>(
|
||||
|
@ -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);
|
||||
|
@ -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(
|
||||
|
Reference in New Issue
Block a user