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:
@ -1,3 +1,5 @@
|
|||||||
|
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/checkbox.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/text.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:integration_test/integration_test.dart';
|
import 'package:integration_test/integration_test.dart';
|
||||||
@ -45,14 +47,14 @@ void main() {
|
|||||||
await tester.assertNumberOfRowsInGridPage(1);
|
await tester.assertNumberOfRowsInGridPage(1);
|
||||||
|
|
||||||
// open the menu to delete the filter
|
// open the menu to delete the filter
|
||||||
await tester.tapTextFilterDisclosureButtonInGrid();
|
await tester.tapDisclosureButtonInFinder(find.byType(TextFilterEditor));
|
||||||
await tester.tapDeleteTextFilterButtonInGrid();
|
await tester.tapDeleteFilterButtonInGrid();
|
||||||
await tester.assertNumberOfRowsInGridPage(10);
|
await tester.assertNumberOfRowsInGridPage(10);
|
||||||
|
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('add checklist filter', (tester) async {
|
testWidgets('add checkbox filter', (tester) async {
|
||||||
await tester.openV020database();
|
await tester.openV020database();
|
||||||
|
|
||||||
// create a filter
|
// create a filter
|
||||||
@ -66,6 +68,90 @@ void main() {
|
|||||||
await tester.tapUnCheckedButtonOnCheckboxFilter();
|
await tester.tapUnCheckedButtonOnCheckboxFilter();
|
||||||
await tester.assertNumberOfRowsInGridPage(5);
|
await tester.assertNumberOfRowsInGridPage(5);
|
||||||
|
|
||||||
|
await tester
|
||||||
|
.tapDisclosureButtonInFinder(find.byType(CheckboxFilterEditor));
|
||||||
|
await tester.tapDeleteFilterButtonInGrid();
|
||||||
|
await tester.assertNumberOfRowsInGridPage(10);
|
||||||
|
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('add checklist filter', (tester) async {
|
||||||
|
await tester.openV020database();
|
||||||
|
|
||||||
|
// create a filter
|
||||||
|
await tester.tapDatabaseFilterButton();
|
||||||
|
await tester.tapCreateFilterByFieldType(FieldType.Checklist, 'checklist');
|
||||||
|
|
||||||
|
// By default, the condition of checklist filter is 'uncompleted'
|
||||||
|
await tester.assertNumberOfRowsInGridPage(9);
|
||||||
|
|
||||||
|
await tester.tapFilterButtonInGrid('checklist');
|
||||||
|
await tester.tapChecklistFilterButtonInGrid();
|
||||||
|
|
||||||
|
await tester.tapCompletedButtonOnChecklistFilter();
|
||||||
|
await tester.assertNumberOfRowsInGridPage(1);
|
||||||
|
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('add single select filter', (tester) async {
|
||||||
|
await tester.openV020database();
|
||||||
|
|
||||||
|
// create a filter
|
||||||
|
await tester.tapDatabaseFilterButton();
|
||||||
|
await tester.tapCreateFilterByFieldType(FieldType.SingleSelect, 'Type');
|
||||||
|
|
||||||
|
await tester.tapFilterButtonInGrid('Type');
|
||||||
|
|
||||||
|
// select the option 's6'
|
||||||
|
await tester.tapOptionFilterWithName('s6');
|
||||||
|
await tester.assertNumberOfRowsInGridPage(0);
|
||||||
|
|
||||||
|
// unselect the option 's6'
|
||||||
|
await tester.tapOptionFilterWithName('s6');
|
||||||
|
await tester.assertNumberOfRowsInGridPage(10);
|
||||||
|
|
||||||
|
// select the option 's5'
|
||||||
|
await tester.tapOptionFilterWithName('s5');
|
||||||
|
await tester.assertNumberOfRowsInGridPage(1);
|
||||||
|
|
||||||
|
// select the option 's4'
|
||||||
|
await tester.tapOptionFilterWithName('s4');
|
||||||
|
|
||||||
|
// The row with 's4' or 's5' should be shown.
|
||||||
|
await tester.assertNumberOfRowsInGridPage(2);
|
||||||
|
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('add multi select filter', (tester) async {
|
||||||
|
await tester.openV020database();
|
||||||
|
|
||||||
|
// create a filter
|
||||||
|
await tester.tapDatabaseFilterButton();
|
||||||
|
await tester.tapCreateFilterByFieldType(
|
||||||
|
FieldType.MultiSelect,
|
||||||
|
'multi-select',
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.tapFilterButtonInGrid('multi-select');
|
||||||
|
await tester.scrollOptionFilterListByOffset(const Offset(0, -200));
|
||||||
|
|
||||||
|
// select the option 'm1'. Any option with 'm1' should be shown.
|
||||||
|
await tester.tapOptionFilterWithName('m1');
|
||||||
|
await tester.assertNumberOfRowsInGridPage(5);
|
||||||
|
await tester.tapOptionFilterWithName('m1');
|
||||||
|
|
||||||
|
// select the option 'm2'. Any option with 'm2' should be shown.
|
||||||
|
await tester.tapOptionFilterWithName('m2');
|
||||||
|
await tester.assertNumberOfRowsInGridPage(4);
|
||||||
|
await tester.tapOptionFilterWithName('m2');
|
||||||
|
|
||||||
|
// select the option 'm4'. Any option with 'm4' should be shown.
|
||||||
|
await tester.tapOptionFilterWithName('m4');
|
||||||
|
await tester.assertNumberOfRowsInGridPage(1);
|
||||||
|
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -85,7 +85,10 @@ extension AppFlowyTestBase on WidgetTester {
|
|||||||
bool warnIfMissed = true,
|
bool warnIfMissed = true,
|
||||||
int milliseconds = 500,
|
int milliseconds = 500,
|
||||||
}) async {
|
}) async {
|
||||||
await tap(finder);
|
await tap(
|
||||||
|
finder,
|
||||||
|
warnIfMissed: warnIfMissed,
|
||||||
|
);
|
||||||
await pumpAndSettle(Duration(milliseconds: milliseconds));
|
await pumpAndSettle(Duration(milliseconds: milliseconds));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,9 @@ import 'package:appflowy/plugins/database_view/application/setting/setting_bloc.
|
|||||||
import 'package:appflowy/plugins/database_view/board/presentation/board_page.dart';
|
import 'package:appflowy/plugins/database_view/board/presentation/board_page.dart';
|
||||||
import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_page.dart';
|
import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_page.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/checkbox.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/checkbox.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/checklist/checklist.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/select_option/option_list.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/select_option/select_option.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/text.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/text.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/create_filter_list.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/create_filter_list.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/disclosure_button.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/disclosure_button.dart';
|
||||||
@ -555,6 +558,11 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
|||||||
await tapButton(button);
|
await tapButton(button);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> scrollOptionFilterListByOffset(Offset offset) async {
|
||||||
|
await drag(find.byType(SelectOptionFilterEditor), offset);
|
||||||
|
await pumpAndSettle();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> enterTextInTextFilter(String text) async {
|
Future<void> enterTextInTextFilter(String text) async {
|
||||||
final findEditor = find.byType(TextFilterEditor);
|
final findEditor = find.byType(TextFilterEditor);
|
||||||
final findTextField = find.descendant(
|
final findTextField = find.descendant(
|
||||||
@ -566,18 +574,17 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
|||||||
await pumpAndSettle(const Duration(milliseconds: 300));
|
await pumpAndSettle(const Duration(milliseconds: 300));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> tapTextFilterDisclosureButtonInGrid() async {
|
Future<void> tapDisclosureButtonInFinder(Finder finder) async {
|
||||||
final findEditor = find.byType(TextFilterEditor);
|
|
||||||
final findDisclosure = find.descendant(
|
final findDisclosure = find.descendant(
|
||||||
of: findEditor,
|
of: finder,
|
||||||
matching: find.byType(DisclosureButton),
|
matching: find.byType(DisclosureButton),
|
||||||
);
|
);
|
||||||
|
|
||||||
await tapButton(findDisclosure);
|
await tapButton(findDisclosure);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// must call [tapTextFilterDisclosureButtonInGrid] first.
|
/// must call [tapDisclosureButtonInFinder] first.
|
||||||
Future<void> tapDeleteTextFilterButtonInGrid() async {
|
Future<void> tapDeleteFilterButtonInGrid() async {
|
||||||
await tapButton(find.text(LocaleKeys.grid_settings_deleteFilter.tr()));
|
await tapButton(find.text(LocaleKeys.grid_settings_deleteFilter.tr()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -585,6 +592,25 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
|||||||
await tapButton(find.byType(CheckboxFilterConditionList));
|
await tapButton(find.byType(CheckboxFilterConditionList));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> tapChecklistFilterButtonInGrid() async {
|
||||||
|
await tapButton(find.byType(ChecklistFilterConditionList));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The [SelectOptionFilterList] must show up first.
|
||||||
|
Future<void> tapOptionFilterWithName(String name) async {
|
||||||
|
final findCell = find.descendant(
|
||||||
|
of: find.byType(SelectOptionFilterList),
|
||||||
|
matching: find.byWidgetPredicate(
|
||||||
|
(widget) =>
|
||||||
|
widget is SelectOptionFilterCell && widget.option.name == name,
|
||||||
|
skipOffstage: false,
|
||||||
|
),
|
||||||
|
skipOffstage: false,
|
||||||
|
);
|
||||||
|
expect(findCell, findsOneWidget);
|
||||||
|
await tapButton(findCell, warnIfMissed: false);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> tapCheckedButtonOnCheckboxFilter() async {
|
Future<void> tapCheckedButtonOnCheckboxFilter() async {
|
||||||
final button = find.descendant(
|
final button = find.descendant(
|
||||||
of: find.byType(HoverButton),
|
of: find.byType(HoverButton),
|
||||||
@ -603,6 +629,24 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
|||||||
await tapButton(button);
|
await tapButton(button);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> tapCompletedButtonOnChecklistFilter() async {
|
||||||
|
final button = find.descendant(
|
||||||
|
of: find.byType(HoverButton),
|
||||||
|
matching: find.text(LocaleKeys.grid_checklistFilter_isComplete.tr()),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tapButton(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> tapUnCompletedButtonOnChecklistFilter() async {
|
||||||
|
final button = find.descendant(
|
||||||
|
of: find.byType(HoverButton),
|
||||||
|
matching: find.text(LocaleKeys.grid_checklistFilter_isIncomplted.tr()),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tapButton(button);
|
||||||
|
}
|
||||||
|
|
||||||
/// Should call [tapDatabaseSettingButton] first.
|
/// Should call [tapDatabaseSettingButton] first.
|
||||||
Future<void> tapDatabaseLayoutButton() async {
|
Future<void> tapDatabaseLayoutButton() async {
|
||||||
final findSettingItem = find.byType(DatabaseSettingItem);
|
final findSettingItem = find.byType(DatabaseSettingItem);
|
||||||
|
@ -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/plugins/database_view/application/view/view_cache.dart';
|
||||||
import 'package:appflowy_backend/log.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/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/field_entities.pbenum.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/group.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/group.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/group_changeset.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 {
|
Future<Either<Unit, FlowyError>> open() async {
|
||||||
return _databaseViewBackendSvc.openGrid().then((result) {
|
return _databaseViewBackendSvc.openGrid().then((result) {
|
||||||
return result.fold(
|
return result.fold(
|
||||||
(database) async {
|
(DatabasePB database) async {
|
||||||
databaseLayout = database.layoutType;
|
databaseLayout = database.layoutType;
|
||||||
|
|
||||||
|
// Listen on layout changed if database layout is calendar
|
||||||
if (databaseLayout == DatabaseLayoutPB.Calendar) {
|
if (databaseLayout == DatabaseLayoutPB.Calendar) {
|
||||||
_listenOnCalendarLayoutChanged();
|
_listenOnCalendarLayoutChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
_databaseCallbacks?.onDatabaseChanged?.call(database);
|
// Load the actual database field data.
|
||||||
_viewCache.rowCache.setInitialRows(database.rows);
|
final fieldsOrFail = await fieldController.loadFields(
|
||||||
return await fieldController
|
|
||||||
.loadFields(
|
|
||||||
fieldIds: database.fields,
|
fieldIds: database.fields,
|
||||||
)
|
);
|
||||||
.then(
|
return fieldsOrFail.fold(
|
||||||
(result) {
|
(fields) {
|
||||||
return result.fold(
|
// Notify the database is changed after the fields are loaded.
|
||||||
(l) => Future(() async {
|
// The database won't can't be used until the fields are loaded.
|
||||||
await _loadGroups();
|
_databaseCallbacks?.onDatabaseChanged?.call(database);
|
||||||
await _loadLayoutSetting();
|
_viewCache.rowCache.setInitialRows(database.rows);
|
||||||
return left(l);
|
return Future(() async {
|
||||||
}),
|
await _loadGroups();
|
||||||
(err) => right(err),
|
await _loadLayoutSetting();
|
||||||
);
|
return left(fields);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(err) {
|
||||||
|
Log.error(err);
|
||||||
|
return right(err);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -33,7 +33,8 @@ class _GridFieldNotifier extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<FieldInfo> get fieldInfos => _fieldInfos;
|
UnmodifiableListView<FieldInfo> get fieldInfos =>
|
||||||
|
UnmodifiableListView(_fieldInfos);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _GridFilterNotifier extends ChangeNotifier {
|
class _GridFilterNotifier extends ChangeNotifier {
|
||||||
@ -85,9 +86,11 @@ class FieldController {
|
|||||||
final FilterBackendService _filterBackendSvc;
|
final FilterBackendService _filterBackendSvc;
|
||||||
final SortBackendService _sortBackendSvc;
|
final SortBackendService _sortBackendSvc;
|
||||||
|
|
||||||
|
bool _isDisposed = false;
|
||||||
|
|
||||||
// Field callbacks
|
// Field callbacks
|
||||||
final Map<OnReceiveFields, VoidCallback> _fieldCallbacks = {};
|
final Map<OnReceiveFields, VoidCallback> _fieldCallbacks = {};
|
||||||
_GridFieldNotifier? _fieldNotifier = _GridFieldNotifier();
|
final _GridFieldNotifier _fieldNotifier = _GridFieldNotifier();
|
||||||
|
|
||||||
// Field updated callbacks
|
// Field updated callbacks
|
||||||
final Map<OnReceiveUpdateFields, void Function(List<FieldInfo>)>
|
final Map<OnReceiveUpdateFields, void Function(List<FieldInfo>)>
|
||||||
@ -107,15 +110,15 @@ class FieldController {
|
|||||||
final Map<String, SortPB> _sortPBByFieldId = {};
|
final Map<String, SortPB> _sortPBByFieldId = {};
|
||||||
|
|
||||||
// Getters
|
// Getters
|
||||||
List<FieldInfo> get fieldInfos => [..._fieldNotifier?.fieldInfos ?? []];
|
List<FieldInfo> get fieldInfos => [..._fieldNotifier.fieldInfos];
|
||||||
List<FilterInfo> get filterInfos => [..._filterNotifier?.filters ?? []];
|
List<FilterInfo> get filterInfos => [..._filterNotifier?.filters ?? []];
|
||||||
List<SortInfo> get sortInfos => [..._sortNotifier?.sorts ?? []];
|
List<SortInfo> get sortInfos => [..._sortNotifier?.sorts ?? []];
|
||||||
|
|
||||||
FieldInfo? getField(String fieldId) {
|
FieldInfo? getField(String fieldId) {
|
||||||
final fields = _fieldNotifier?.fieldInfos
|
final fields = _fieldNotifier.fieldInfos
|
||||||
.where((element) => element.id == fieldId)
|
.where((element) => element.id == fieldId)
|
||||||
.toList() ??
|
.toList();
|
||||||
[];
|
|
||||||
if (fields.isEmpty) {
|
if (fields.isEmpty) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -169,6 +172,10 @@ class FieldController {
|
|||||||
_listenOnSortChanged();
|
_listenOnSortChanged();
|
||||||
|
|
||||||
_settingBackendSvc.getSetting().then((result) {
|
_settingBackendSvc.getSetting().then((result) {
|
||||||
|
if (_isDisposed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
result.fold(
|
result.fold(
|
||||||
(setting) => _updateSetting(setting),
|
(setting) => _updateSetting(setting),
|
||||||
(err) => Log.error(err),
|
(err) => Log.error(err),
|
||||||
@ -257,6 +264,10 @@ class FieldController {
|
|||||||
|
|
||||||
_filtersListener.start(
|
_filtersListener.start(
|
||||||
onFilterChanged: (result) {
|
onFilterChanged: (result) {
|
||||||
|
if (_isDisposed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
result.fold(
|
result.fold(
|
||||||
(FilterChangesetNotificationPB changeset) {
|
(FilterChangesetNotificationPB changeset) {
|
||||||
final List<FilterInfo> filters = filterInfos;
|
final List<FilterInfo> filters = filterInfos;
|
||||||
@ -351,6 +362,9 @@ class FieldController {
|
|||||||
|
|
||||||
_sortsListener.start(
|
_sortsListener.start(
|
||||||
onSortChanged: (result) {
|
onSortChanged: (result) {
|
||||||
|
if (_isDisposed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
result.fold(
|
result.fold(
|
||||||
(SortChangesetNotificationPB changeset) {
|
(SortChangesetNotificationPB changeset) {
|
||||||
final List<SortInfo> newSortInfos = sortInfos;
|
final List<SortInfo> newSortInfos = sortInfos;
|
||||||
@ -371,6 +385,10 @@ class FieldController {
|
|||||||
//Listen on setting changes
|
//Listen on setting changes
|
||||||
_settingListener.start(
|
_settingListener.start(
|
||||||
onSettingUpdated: (result) {
|
onSettingUpdated: (result) {
|
||||||
|
if (_isDisposed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
result.fold(
|
result.fold(
|
||||||
(setting) => _updateSetting(setting),
|
(setting) => _updateSetting(setting),
|
||||||
(r) => Log.error(r),
|
(r) => Log.error(r),
|
||||||
@ -385,6 +403,9 @@ class FieldController {
|
|||||||
onFieldsChanged: (result) {
|
onFieldsChanged: (result) {
|
||||||
result.fold(
|
result.fold(
|
||||||
(changeset) {
|
(changeset) {
|
||||||
|
if (_isDisposed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
_deleteFields(changeset.deletedFields);
|
_deleteFields(changeset.deletedFields);
|
||||||
_insertFields(changeset.insertedFields);
|
_insertFields(changeset.insertedFields);
|
||||||
|
|
||||||
@ -417,27 +438,29 @@ class FieldController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _updateFieldInfos() {
|
void _updateFieldInfos() {
|
||||||
if (_fieldNotifier != null) {
|
for (final field in _fieldNotifier.fieldInfos) {
|
||||||
for (final field in _fieldNotifier!.fieldInfos) {
|
field._isGroupField = _groupConfigurationByFieldId[field.id] != null;
|
||||||
field._isGroupField = _groupConfigurationByFieldId[field.id] != null;
|
field._hasFilter = _filterPBByFieldId[field.id] != null;
|
||||||
field._hasFilter = _filterPBByFieldId[field.id] != null;
|
field._hasSort = _sortPBByFieldId[field.id] != null;
|
||||||
field._hasSort = _sortPBByFieldId[field.id] != null;
|
|
||||||
}
|
|
||||||
_fieldNotifier?.notify();
|
|
||||||
}
|
}
|
||||||
|
_fieldNotifier.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
|
if (_isDisposed) {
|
||||||
|
Log.warn('FieldController is already disposed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_isDisposed = true;
|
||||||
await _fieldListener.stop();
|
await _fieldListener.stop();
|
||||||
await _filtersListener.stop();
|
await _filtersListener.stop();
|
||||||
await _settingListener.stop();
|
await _settingListener.stop();
|
||||||
await _sortsListener.stop();
|
await _sortsListener.stop();
|
||||||
|
|
||||||
for (final callback in _fieldCallbacks.values) {
|
for (final callback in _fieldCallbacks.values) {
|
||||||
_fieldNotifier?.removeListener(callback);
|
_fieldNotifier.removeListener(callback);
|
||||||
}
|
}
|
||||||
_fieldNotifier?.dispose();
|
_fieldNotifier.dispose();
|
||||||
_fieldNotifier = null;
|
|
||||||
|
|
||||||
for (final callback in _filterCallbacks.values) {
|
for (final callback in _filterCallbacks.values) {
|
||||||
_filterNotifier?.removeListener(callback);
|
_filterNotifier?.removeListener(callback);
|
||||||
@ -460,7 +483,11 @@ class FieldController {
|
|||||||
return Future(
|
return Future(
|
||||||
() => result.fold(
|
() => result.fold(
|
||||||
(newFields) {
|
(newFields) {
|
||||||
_fieldNotifier?.fieldInfos =
|
if (_isDisposed) {
|
||||||
|
return left(unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
_fieldNotifier.fieldInfos =
|
||||||
newFields.map((field) => FieldInfo(field: field)).toList();
|
newFields.map((field) => FieldInfo(field: field)).toList();
|
||||||
_loadFilters();
|
_loadFilters();
|
||||||
_loadSorts();
|
_loadSorts();
|
||||||
@ -551,7 +578,7 @@ class FieldController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_fieldCallbacks[onReceiveFields] = callback;
|
_fieldCallbacks[onReceiveFields] = callback;
|
||||||
_fieldNotifier?.addListener(callback);
|
_fieldNotifier.addListener(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onFilters != null) {
|
if (onFilters != null) {
|
||||||
@ -588,7 +615,7 @@ class FieldController {
|
|||||||
if (onFieldsListener != null) {
|
if (onFieldsListener != null) {
|
||||||
final callback = _fieldCallbacks.remove(onFieldsListener);
|
final callback = _fieldCallbacks.remove(onFieldsListener);
|
||||||
if (callback != null) {
|
if (callback != null) {
|
||||||
_fieldNotifier?.removeListener(callback);
|
_fieldNotifier.removeListener(callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (onFiltersListener != null) {
|
if (onFiltersListener != null) {
|
||||||
@ -616,7 +643,7 @@ class FieldController {
|
|||||||
};
|
};
|
||||||
|
|
||||||
newFields.retainWhere((field) => (deletedFieldMap[field.id] == null));
|
newFields.retainWhere((field) => (deletedFieldMap[field.id] == null));
|
||||||
_fieldNotifier?.fieldInfos = newFields;
|
_fieldNotifier.fieldInfos = newFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _insertFields(List<IndexFieldPB> insertedFields) {
|
void _insertFields(List<IndexFieldPB> insertedFields) {
|
||||||
@ -632,7 +659,7 @@ class FieldController {
|
|||||||
newFieldInfos.add(fieldInfo);
|
newFieldInfos.add(fieldInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_fieldNotifier?.fieldInfos = newFieldInfos;
|
_fieldNotifier.fieldInfos = newFieldInfos;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<FieldInfo> _updateFields(List<FieldPB> updatedFieldPBs) {
|
List<FieldInfo> _updateFields(List<FieldPB> updatedFieldPBs) {
|
||||||
@ -654,7 +681,7 @@ class FieldController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (updatedFields.isNotEmpty) {
|
if (updatedFields.isNotEmpty) {
|
||||||
_fieldNotifier?.fieldInfos = newFields;
|
_fieldNotifier.fieldInfos = newFields;
|
||||||
}
|
}
|
||||||
return updatedFields;
|
return updatedFields;
|
||||||
}
|
}
|
||||||
|
@ -29,9 +29,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
|||||||
String get viewId => databaseController.viewId;
|
String get viewId => databaseController.viewId;
|
||||||
|
|
||||||
BoardBloc({required ViewPB view})
|
BoardBloc({required ViewPB view})
|
||||||
: databaseController = DatabaseController(
|
: databaseController = DatabaseController(view: view),
|
||||||
view: view,
|
|
||||||
),
|
|
||||||
super(BoardState.initial(view.id)) {
|
super(BoardState.initial(view.id)) {
|
||||||
boardController = AppFlowyBoardController(
|
boardController = AppFlowyBoardController(
|
||||||
onMoveGroup: (
|
onMoveGroup: (
|
||||||
|
@ -32,15 +32,13 @@ import 'widgets/shortcuts.dart';
|
|||||||
import 'widgets/toolbar/grid_toolbar.dart';
|
import 'widgets/toolbar/grid_toolbar.dart';
|
||||||
|
|
||||||
class GridPage extends StatefulWidget {
|
class GridPage extends StatefulWidget {
|
||||||
GridPage({
|
const GridPage({
|
||||||
required this.view,
|
required this.view,
|
||||||
this.onDeleted,
|
this.onDeleted,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : databaseController = DatabaseController(view: view),
|
}) : super(key: key);
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
final ViewPB view;
|
final ViewPB view;
|
||||||
final DatabaseController databaseController;
|
|
||||||
final VoidCallback? onDeleted;
|
final VoidCallback? onDeleted;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -48,6 +46,14 @@ class GridPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _GridPageState extends State<GridPage> {
|
class _GridPageState extends State<GridPage> {
|
||||||
|
late DatabaseController databaseController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
databaseController = DatabaseController(view: widget.view);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MultiBlocProvider(
|
return MultiBlocProvider(
|
||||||
@ -55,19 +61,19 @@ class _GridPageState extends State<GridPage> {
|
|||||||
BlocProvider<GridBloc>(
|
BlocProvider<GridBloc>(
|
||||||
create: (context) => GridBloc(
|
create: (context) => GridBloc(
|
||||||
view: widget.view,
|
view: widget.view,
|
||||||
databaseController: widget.databaseController,
|
databaseController: databaseController,
|
||||||
)..add(const GridEvent.initial()),
|
)..add(const GridEvent.initial()),
|
||||||
),
|
),
|
||||||
BlocProvider<GridFilterMenuBloc>(
|
BlocProvider<GridFilterMenuBloc>(
|
||||||
create: (context) => GridFilterMenuBloc(
|
create: (context) => GridFilterMenuBloc(
|
||||||
viewId: widget.view.id,
|
viewId: widget.view.id,
|
||||||
fieldController: widget.databaseController.fieldController,
|
fieldController: databaseController.fieldController,
|
||||||
)..add(const GridFilterMenuEvent.initial()),
|
)..add(const GridFilterMenuEvent.initial()),
|
||||||
),
|
),
|
||||||
BlocProvider<SortMenuBloc>(
|
BlocProvider<SortMenuBloc>(
|
||||||
create: (context) => SortMenuBloc(
|
create: (context) => SortMenuBloc(
|
||||||
viewId: widget.view.id,
|
viewId: widget.view.id,
|
||||||
fieldController: widget.databaseController.fieldController,
|
fieldController: databaseController.fieldController,
|
||||||
)..add(const SortMenuEvent.initial()),
|
)..add(const SortMenuEvent.initial()),
|
||||||
),
|
),
|
||||||
BlocProvider<DatabaseSettingBloc>(
|
BlocProvider<DatabaseSettingBloc>(
|
||||||
|
@ -93,7 +93,7 @@ class ChecklistState extends State<ChecklistFilterEditor> {
|
|||||||
children: [
|
children: [
|
||||||
FlowyText(state.filterInfo.fieldInfo.name),
|
FlowyText(state.filterInfo.fieldInfo.name),
|
||||||
const HSpace(4),
|
const HSpace(4),
|
||||||
ChecklistFilterConditionPBList(
|
ChecklistFilterConditionList(
|
||||||
filterInfo: state.filterInfo,
|
filterInfo: state.filterInfo,
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
@ -118,9 +118,9 @@ class ChecklistState extends State<ChecklistFilterEditor> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChecklistFilterConditionPBList extends StatelessWidget {
|
class ChecklistFilterConditionList extends StatelessWidget {
|
||||||
final FilterInfo filterInfo;
|
final FilterInfo filterInfo;
|
||||||
const ChecklistFilterConditionPBList({
|
const ChecklistFilterConditionList({
|
||||||
required this.filterInfo,
|
required this.filterInfo,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(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/plugins/database_view/grid/presentation/layout/sizes.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart';
|
||||||
import 'package:flowy_infra/image.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:flowy_infra_ui/widget/spacing.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -58,16 +57,16 @@ class SelectOptionFilterList extends StatelessWidget {
|
|||||||
SelectOptionFilterListState>(
|
SelectOptionFilterListState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return ListView.separated(
|
return ListView.separated(
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
controller: ScrollController(),
|
controller: ScrollController(),
|
||||||
itemCount: state.visibleOptions.length,
|
itemCount: state.visibleOptions.length,
|
||||||
separatorBuilder: (context, index) {
|
separatorBuilder: (context, index) {
|
||||||
return VSpace(GridSize.typeOptionSeparatorHeight);
|
return VSpace(GridSize.typeOptionSeparatorHeight);
|
||||||
},
|
},
|
||||||
physics: StyledScrollPhysics(),
|
|
||||||
itemBuilder: (BuildContext context, int index) {
|
itemBuilder: (BuildContext context, int index) {
|
||||||
final option = state.visibleOptions[index];
|
final option = state.visibleOptions[index];
|
||||||
return _SelectOptionFilterCell(
|
return SelectOptionFilterCell(
|
||||||
option: option.optionPB,
|
option: option.optionPB,
|
||||||
isSelected: option.isSelected,
|
isSelected: option.isSelected,
|
||||||
);
|
);
|
||||||
@ -80,21 +79,20 @@ class SelectOptionFilterList extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SelectOptionFilterCell extends StatefulWidget {
|
class SelectOptionFilterCell extends StatefulWidget {
|
||||||
final SelectOptionPB option;
|
final SelectOptionPB option;
|
||||||
final bool isSelected;
|
final bool isSelected;
|
||||||
const _SelectOptionFilterCell({
|
const SelectOptionFilterCell({
|
||||||
required this.option,
|
required this.option,
|
||||||
required this.isSelected,
|
required this.isSelected,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_SelectOptionFilterCell> createState() =>
|
State<SelectOptionFilterCell> createState() => _SelectOptionFilterCellState();
|
||||||
_SelectOptionFilterCellState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SelectOptionFilterCellState extends State<_SelectOptionFilterCell> {
|
class _SelectOptionFilterCellState extends State<SelectOptionFilterCell> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
|
@ -45,7 +45,7 @@ if is_empty ${ret}
|
|||||||
end
|
end
|
||||||
ret = which protoc-gen-dart
|
ret = which protoc-gen-dart
|
||||||
if is_empty ${ret}
|
if is_empty ${ret}
|
||||||
exec cmd.exe /c dart pub global activate protoc_plugin
|
exec cmd.exe /c dart pub global activate protoc_plugin 20.0.1
|
||||||
end
|
end
|
||||||
ret = which protoc-gen-dart
|
ret = which protoc-gen-dart
|
||||||
if is_empty ${ret}
|
if is_empty ${ret}
|
||||||
@ -60,14 +60,14 @@ script_runner = "@duckscript"
|
|||||||
[tasks.install_flutter_protobuf_compiler]
|
[tasks.install_flutter_protobuf_compiler]
|
||||||
script = """
|
script = """
|
||||||
echo "Install protoc_plugin (Dart)"
|
echo "Install protoc_plugin (Dart)"
|
||||||
dart pub global activate protoc_plugin
|
dart pub global activate protoc_plugin 20.0.1
|
||||||
"""
|
"""
|
||||||
script_runner = "@shell"
|
script_runner = "@shell"
|
||||||
|
|
||||||
[tasks.install_flutter_protobuf_compiler.linux]
|
[tasks.install_flutter_protobuf_compiler.linux]
|
||||||
script = """
|
script = """
|
||||||
echo "Install protoc_plugin (Dart)"
|
echo "Install protoc_plugin (Dart)"
|
||||||
dart pub global activate protoc_plugin
|
dart pub global activate protoc_plugin 20.0.1
|
||||||
"""
|
"""
|
||||||
script_runner = "@shell"
|
script_runner = "@shell"
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user