Просмотр исходного кода

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
Nathan.fooo 1 год назад
Родитель
Сommit
14dee6b797

+ 89 - 3
frontend/appflowy_flutter/integration_test/database_filter_test.dart

@@ -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.tapDeleteTextFilterButtonInGrid();
+      await tester.tapDisclosureButtonInFinder(find.byType(TextFilterEditor));
+      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();
     });
     });
   });
   });

+ 4 - 1
frontend/appflowy_flutter/integration_test/util/base.dart

@@ -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;
   }
   }

+ 49 - 5
frontend/appflowy_flutter/integration_test/util/database_test_op.dart

@@ -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 {
-    final findEditor = find.byType(TextFilterEditor);
+  Future<void> tapDisclosureButtonInFinder(Finder finder) async {
     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.
-  Future<void> tapDeleteTextFilterButtonInGrid() async {
+  /// must call [tapDisclosureButtonInFinder] first.
+  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);

+ 21 - 16
frontend/appflowy_flutter/lib/plugins/database_view/application/database_controller.dart

@@ -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);
-          _viewCache.rowCache.setInitialRows(database.rows);
-          return await fieldController
-              .loadFields(
+          // Load the actual database field data.
+          final fieldsOrFail = await fieldController.loadFields(
             fieldIds: database.fields,
             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);
             },
             },
           );
           );
         },
         },

+ 50 - 23
frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_controller.dart

@@ -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
-            .where((element) => element.id == fieldId)
-            .toList() ??
-        [];
+    final fields = _fieldNotifier.fieldInfos
+        .where((element) => element.id == fieldId)
+        .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) {
-        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 {
   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 = null;
+    _fieldNotifier.dispose();
 
 
     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;
   }
   }

+ 1 - 3
frontend/appflowy_flutter/lib/plugins/database_view/board/application/board_bloc.dart

@@ -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(
-          view: view,
-        ),
+      : databaseController = DatabaseController(view: view),
         super(BoardState.initial(view.id)) {
         super(BoardState.initial(view.id)) {
     boardController = AppFlowyBoardController(
     boardController = AppFlowyBoardController(
       onMoveGroup: (
       onMoveGroup: (

+ 13 - 7
frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/grid_page.dart

@@ -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>(

+ 3 - 3
frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/choicechip/checklist/checklist.dart

@@ -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);

+ 6 - 8
frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/choicechip/select_option/option_list.dart

@@ -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() =>
-      _SelectOptionFilterCellState();
+  State<SelectOptionFilterCell> createState() => _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(

+ 3 - 3
frontend/scripts/makefile/protobuf.toml

@@ -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"