fix: filter UI bugs (#1489)

* chore: remove the add filter button if there is no filters can not be added

* fix: update field info after filter was changed

* chore: update filter choicechip ui

* chore: insert and delete one by one to keep the delete/insert index is right

* chore: show filter after creating the default filter

* chore: update textfield_tags version to calm the warnings

* chore: try to fix potential fails on backend test

Co-authored-by: nathan <nathan@appflowy.io>
This commit is contained in:
Nathan.fooo 2022-11-27 14:47:11 +08:00 committed by GitHub
parent 149c2a2725
commit 182bfae5ad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 251 additions and 136 deletions

View file

@ -175,7 +175,14 @@
"is": "Is", "is": "Is",
"isNot": "Is not", "isNot": "Is not",
"isEmpty": "Is empty", "isEmpty": "Is empty",
"isNotEmpty": "Is not empty" "isNotEmpty": "Is not empty",
"choicechipPrefix": {
"isNot": "Not",
"startWith": "Starts with",
"endWith": "Ends with",
"isEmpty": "is empty",
"isNotEmpty": "is not empty"
}
}, },
"field": { "field": {
"hide": "Hide", "hide": "Hide",

View file

@ -142,6 +142,9 @@ class GridFieldController {
filters.retainWhere( filters.retainWhere(
(element) => !deleteFilterIds.contains(element.filter.id), (element) => !deleteFilterIds.contains(element.filter.id),
); );
_filterPBByFieldId.removeWhere(
(key, value) => deleteFilterIds.contains(value.id));
} }
// Inserts the new filter if it's not exist // Inserts the new filter if it's not exist
@ -151,6 +154,7 @@ class GridFieldController {
if (filterIndex == -1) { if (filterIndex == -1) {
final fieldInfo = _findFieldInfoForFilter(fieldInfos, newFilter); final fieldInfo = _findFieldInfoForFilter(fieldInfos, newFilter);
if (fieldInfo != null) { if (fieldInfo != null) {
_filterPBByFieldId[fieldInfo.id] = newFilter;
filters.add(FilterInfo(gridId, newFilter, fieldInfo)); filters.add(FilterInfo(gridId, newFilter, fieldInfo));
} }
} }
@ -187,10 +191,9 @@ class GridFieldController {
} }
_filterPBByFieldId[fieldInfo.id] = updatedFilter.filter; _filterPBByFieldId[fieldInfo.id] = updatedFilter.filter;
} }
}
}
_updateFieldInfos(); _updateFieldInfos();
}
}
_filterNotifier?.filters = filters; _filterNotifier?.filters = filters;
}, },
(err) => Log.error(err), (err) => Log.error(err),
@ -345,7 +348,6 @@ class GridFieldController {
} }
_filterCallbacks[onFilters] = callback; _filterCallbacks[onFilters] = callback;
callback();
_filterNotifier?.addListener(callback); _filterNotifier?.addListener(callback);
} }
} }

View file

@ -77,7 +77,7 @@ class GridCreateFilterBloc
void _startListening() { void _startListening() {
_onFieldFn = (fields) { _onFieldFn = (fields) {
fields.retainWhere((field) => field.hasFilter == false); fields.retainWhere((field) => field.canCreateFilter);
add(GridCreateFilterEvent.didReceiveFields(fields)); add(GridCreateFilterEvent.didReceiveFields(fields));
}; };
fieldController.addListener(onFields: _onFieldFn); fieldController.addListener(onFields: _onFieldFn);

View file

@ -11,6 +11,7 @@ class GridFilterMenuBloc
final String viewId; final String viewId;
final GridFieldController fieldController; final GridFieldController fieldController;
void Function(List<FilterInfo>)? _onFilterFn; void Function(List<FilterInfo>)? _onFilterFn;
void Function(List<FieldInfo>)? _onFieldFn;
GridFilterMenuBloc({required this.viewId, required this.fieldController}) GridFilterMenuBloc({required this.viewId, required this.fieldController})
: super(GridFilterMenuState.initial( : super(GridFilterMenuState.initial(
@ -32,7 +33,12 @@ class GridFilterMenuBloc
emit(state.copyWith(isVisible: isVisible)); emit(state.copyWith(isVisible: isVisible));
}, },
didReceiveFields: (List<FieldInfo> fields) { didReceiveFields: (List<FieldInfo> fields) {
emit(state.copyWith(fields: fields)); emit(
state.copyWith(
fields: fields,
creatableFields: getCreatableFilter(fields),
),
);
}, },
); );
}, },
@ -44,9 +50,18 @@ class GridFilterMenuBloc
add(GridFilterMenuEvent.didReceiveFilters(filters)); add(GridFilterMenuEvent.didReceiveFilters(filters));
}; };
fieldController.addListener(onFilters: (filters) { _onFieldFn = (fields) {
add(GridFilterMenuEvent.didReceiveFields(fields));
};
fieldController.addListener(
onFilters: (filters) {
_onFilterFn?.call(filters); _onFilterFn?.call(filters);
}); },
onFields: (fields) {
_onFieldFn?.call(fields);
},
);
} }
@override @override
@ -55,6 +70,10 @@ class GridFilterMenuBloc
fieldController.removeListener(onFiltersListener: _onFilterFn!); fieldController.removeListener(onFiltersListener: _onFilterFn!);
_onFilterFn = null; _onFilterFn = null;
} }
if (_onFieldFn != null) {
fieldController.removeListener(onFieldsListener: _onFieldFn!);
_onFieldFn = null;
}
return super.close(); return super.close();
} }
} }
@ -75,6 +94,7 @@ class GridFilterMenuState with _$GridFilterMenuState {
required String viewId, required String viewId,
required List<FilterInfo> filters, required List<FilterInfo> filters,
required List<FieldInfo> fields, required List<FieldInfo> fields,
required List<FieldInfo> creatableFields,
required bool isVisible, required bool isVisible,
}) = _GridFilterMenuState; }) = _GridFilterMenuState;
@ -87,6 +107,13 @@ class GridFilterMenuState with _$GridFilterMenuState {
viewId: viewId, viewId: viewId,
filters: filterInfos, filters: filterInfos,
fields: fields, fields: fields,
creatableFields: getCreatableFilter(fields),
isVisible: false, isVisible: false,
); );
} }
List<FieldInfo> getCreatableFilter(List<FieldInfo> fieldInfos) {
final List<FieldInfo> creatableFields = List.from(fieldInfos);
creatableFields.retainWhere((element) => element.canCreateFilter);
return creatableFields;
}

View file

@ -1,5 +1,4 @@
import 'package:app_flowy/plugins/grid/presentation/widgets/filter/filter_info.dart'; import 'package:app_flowy/plugins/grid/presentation/widgets/filter/filter_info.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/text_filter.pbserver.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/text_filter.pbserver.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/util.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/util.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -30,26 +29,20 @@ class TextFilterEditorBloc
_startListening(); _startListening();
}, },
updateCondition: (TextFilterCondition condition) { updateCondition: (TextFilterCondition condition) {
final textFilter = filterInfo.textFilter()!;
_ffiService.insertTextFilter( _ffiService.insertTextFilter(
filterId: filterInfo.filter.id, filterId: filterInfo.filter.id,
fieldId: filterInfo.field.id, fieldId: filterInfo.field.id,
condition: condition, condition: condition,
content: textFilter.content, content: state.filter.content,
); );
}, },
updateContent: (content) { updateContent: (content) {
final textFilter = filterInfo.textFilter();
if (textFilter != null) {
_ffiService.insertTextFilter( _ffiService.insertTextFilter(
filterId: filterInfo.filter.id, filterId: filterInfo.filter.id,
fieldId: filterInfo.field.id, fieldId: filterInfo.field.id,
condition: textFilter.condition, condition: state.filter.condition,
content: content, content: content,
); );
} else {
Log.error("Invalid text filter");
}
}, },
delete: () { delete: () {
_ffiService.deleteFilter( _ffiService.deleteFilter(
@ -60,7 +53,11 @@ class TextFilterEditorBloc
}, },
didReceiveFilter: (FilterPB filter) { didReceiveFilter: (FilterPB filter) {
final filterInfo = state.filterInfo.copyWith(filter: filter); final filterInfo = state.filterInfo.copyWith(filter: filter);
emit(state.copyWith(filterInfo: filterInfo)); final textFilter = filterInfo.textFilter()!;
emit(state.copyWith(
filterInfo: filterInfo,
filter: textFilter,
));
}, },
); );
}, },
@ -99,12 +96,15 @@ class TextFilterEditorEvent with _$TextFilterEditorEvent {
@freezed @freezed
class TextFilterEditorState with _$TextFilterEditorState { class TextFilterEditorState with _$TextFilterEditorState {
const factory TextFilterEditorState({required FilterInfo filterInfo}) = const factory TextFilterEditorState({
_GridFilterState; required FilterInfo filterInfo,
required TextFilterPB filter,
}) = _GridFilterState;
factory TextFilterEditorState.initial(FilterInfo filterInfo) { factory TextFilterEditorState.initial(FilterInfo filterInfo) {
return TextFilterEditorState( return TextFilterEditorState(
filterInfo: filterInfo, filterInfo: filterInfo,
filter: filterInfo.textFilter()!,
); );
} }
} }

View file

@ -78,22 +78,23 @@ class GridRowCache {
_showRows(changeset.visibleRows); _showRows(changeset.visibleRows);
} }
void _deleteRows(List<String> deletedRows) { void _deleteRows(List<String> deletedRowIds) {
if (deletedRows.isEmpty) return; for (final rowId in deletedRowIds) {
final deletedRow = _rowList.remove(rowId);
final deletedIndex = _rowList.removeRows(deletedRows); if (deletedRow != null) {
if (deletedIndex.isNotEmpty) { _rowChangeReasonNotifier.receive(RowsChangedReason.delete(deletedRow));
_rowChangeReasonNotifier.receive(RowsChangedReason.delete(deletedIndex)); }
} }
} }
void _insertRows(List<InsertedRowPB> insertRows) { void _insertRows(List<InsertedRowPB> insertRows) {
if (insertRows.isEmpty) return; for (final insertedRow in insertRows) {
final insertedIndex =
InsertedIndexs insertIndexs = _rowList.insert(insertedRow.index, buildGridRow(insertedRow.row));
_rowList.insertRows(insertRows, (rowPB) => buildGridRow(rowPB)); if (insertedIndex != null) {
if (insertIndexs.isNotEmpty) { _rowChangeReasonNotifier
_rowChangeReasonNotifier.receive(RowsChangedReason.insert(insertIndexs)); .receive(RowsChangedReason.insert(insertedIndex));
}
} }
} }
@ -108,21 +109,22 @@ class GridRowCache {
} }
void _hideRows(List<String> invisibleRows) { void _hideRows(List<String> invisibleRows) {
if (invisibleRows.isEmpty) return; for (final rowId in invisibleRows) {
final deletedRow = _rowList.remove(rowId);
final List<DeletedIndex> deletedRows = _rowList.removeRows(invisibleRows); if (deletedRow != null) {
if (deletedRows.isNotEmpty) { _rowChangeReasonNotifier.receive(RowsChangedReason.delete(deletedRow));
_rowChangeReasonNotifier.receive(RowsChangedReason.delete(deletedRows)); }
} }
} }
void _showRows(List<InsertedRowPB> visibleRows) { void _showRows(List<InsertedRowPB> visibleRows) {
if (visibleRows.isEmpty) return; for (final insertedRow in visibleRows) {
final insertedIndex =
final List<InsertedIndex> insertedRows = _rowList.insert(insertedRow.index, buildGridRow(insertedRow.row));
_rowList.insertRows(visibleRows, (rowPB) => buildGridRow(rowPB)); if (insertedIndex != null) {
if (insertedRows.isNotEmpty) { _rowChangeReasonNotifier
_rowChangeReasonNotifier.receive(RowsChangedReason.insert(insertedRows)); .receive(RowsChangedReason.insert(insertedIndex));
}
} }
} }
@ -274,8 +276,8 @@ typedef UpdatedIndexMap = LinkedHashMap<String, UpdatedIndex>;
@freezed @freezed
class RowsChangedReason with _$RowsChangedReason { class RowsChangedReason with _$RowsChangedReason {
const factory RowsChangedReason.insert(InsertedIndexs items) = _Insert; const factory RowsChangedReason.insert(InsertedIndex item) = _Insert;
const factory RowsChangedReason.delete(DeletedIndexs items) = _Delete; const factory RowsChangedReason.delete(DeletedIndex item) = _Delete;
const factory RowsChangedReason.update(UpdatedIndexMap indexs) = _Update; const factory RowsChangedReason.update(UpdatedIndexMap indexs) = _Update;
const factory RowsChangedReason.fieldDidChange() = _FieldDidChange; const factory RowsChangedReason.fieldDidChange() = _FieldDidChange;
const factory RowsChangedReason.initial() = InitialListState; const factory RowsChangedReason.initial() = InitialListState;

View file

@ -39,10 +39,10 @@ class RowList {
_rowInfoByRowId[rowId] = rowInfo; _rowInfoByRowId[rowId] = rowInfo;
} }
void insert(int index, RowInfo rowInfo) { InsertedIndex? insert(int index, RowInfo rowInfo) {
final rowId = rowInfo.rowPB.id; final rowId = rowInfo.rowPB.id;
var insertedIndex = index; var insertedIndex = index;
if (_rowInfos.length < insertedIndex) { if (_rowInfos.length <= insertedIndex) {
insertedIndex = _rowInfos.length; insertedIndex = _rowInfos.length;
} }
@ -50,13 +50,16 @@ class RowList {
if (oldRowInfo != null) { if (oldRowInfo != null) {
_rowInfos.insert(insertedIndex, rowInfo); _rowInfos.insert(insertedIndex, rowInfo);
_rowInfos.remove(oldRowInfo); _rowInfos.remove(oldRowInfo);
_rowInfoByRowId[rowId] = rowInfo;
return null;
} else { } else {
_rowInfos.insert(insertedIndex, rowInfo); _rowInfos.insert(insertedIndex, rowInfo);
}
_rowInfoByRowId[rowId] = rowInfo; _rowInfoByRowId[rowId] = rowInfo;
return InsertedIndex(index: insertedIndex, rowId: rowId);
}
} }
RowInfo? remove(String rowId) { DeletedIndex? remove(String rowId) {
final rowInfo = _rowInfoByRowId[rowId]; final rowInfo = _rowInfoByRowId[rowId];
if (rowInfo != null) { if (rowInfo != null) {
final index = _rowInfos.indexOf(rowInfo); final index = _rowInfos.indexOf(rowInfo);
@ -64,8 +67,10 @@ class RowList {
_rowInfoByRowId.remove(rowInfo.rowPB.id); _rowInfoByRowId.remove(rowInfo.rowPB.id);
_rowInfos.remove(rowInfo); _rowInfos.remove(rowInfo);
} }
return DeletedIndex(index: index, rowInfo: rowInfo);
} else {
return null;
} }
return rowInfo;
} }
InsertedIndexs insertRows( InsertedIndexs insertRows(

View file

@ -207,20 +207,16 @@ class _GridRowsState extends State<_GridRows> {
return BlocConsumer<GridBloc, GridState>( return BlocConsumer<GridBloc, GridState>(
listenWhen: (previous, current) => previous.reason != current.reason, listenWhen: (previous, current) => previous.reason != current.reason,
listener: (context, state) { listener: (context, state) {
state.reason.mapOrNull( state.reason.whenOrNull(
insert: (value) { insert: (item) {
for (final item in value.items) {
_key.currentState?.insertItem(item.index); _key.currentState?.insertItem(item.index);
}
}, },
delete: (value) { delete: (item) {
for (final item in value.items) {
_key.currentState?.removeItem( _key.currentState?.removeItem(
item.index, item.index,
(context, animation) => (context, animation) =>
_renderRow(context, item.rowInfo, animation), _renderRow(context, item.rowInfo, animation),
); );
}
}, },
); );
}, },

View file

@ -10,19 +10,17 @@ import 'dart:math' as math;
class ChoiceChipButton extends StatelessWidget { class ChoiceChipButton extends StatelessWidget {
final FilterInfo filterInfo; final FilterInfo filterInfo;
final VoidCallback? onTap; final VoidCallback? onTap;
final String filterDesc;
const ChoiceChipButton({ const ChoiceChipButton({
Key? key, Key? key,
required this.filterInfo, required this.filterInfo,
this.filterDesc = '',
this.onTap, this.onTap,
}) : super(key: key); }) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final arrow = Transform.rotate(
angle: -math.pi / 2,
child: svgWidget("home/arrow_left"),
);
final borderSide = BorderSide( final borderSide = BorderSide(
color: AFThemeExtension.of(context).toggleOffFill, color: AFThemeExtension.of(context).toggleOffFill,
width: 1.0, width: 1.0,
@ -46,10 +44,33 @@ class ChoiceChipButton extends StatelessWidget {
filterInfo.field.fieldType.iconName(), filterInfo.field.fieldType.iconName(),
color: Theme.of(context).colorScheme.onSurface, color: Theme.of(context).colorScheme.onSurface,
), ),
rightIcon: arrow, rightIcon: _ChoicechipFilterDesc(filterDesc: filterDesc),
hoverColor: AFThemeExtension.of(context).lightGreyHover, hoverColor: AFThemeExtension.of(context).lightGreyHover,
onTap: onTap, onTap: onTap,
), ),
); );
} }
} }
class _ChoicechipFilterDesc extends StatelessWidget {
final String filterDesc;
const _ChoicechipFilterDesc({this.filterDesc = '', Key? key})
: super(key: key);
@override
Widget build(BuildContext context) {
final arrow = Transform.rotate(
angle: -math.pi / 2,
child: svgWidget("home/arrow_left"),
);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 2),
child: Row(
children: [
if (filterDesc.isNotEmpty) FlowyText(': $filterDesc'),
arrow,
],
),
);
}
}

View file

@ -26,27 +26,62 @@ class TextFilterChoicechip extends StatefulWidget {
} }
class _TextFilterChoicechipState extends State<TextFilterChoicechip> { class _TextFilterChoicechipState extends State<TextFilterChoicechip> {
late TextFilterEditorBloc bloc;
@override
void initState() {
bloc = TextFilterEditorBloc(filterInfo: widget.filterInfo)
..add(const TextFilterEditorEvent.initial());
super.initState();
}
@override
void dispose() {
bloc.close();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider.value(
value: bloc,
child: BlocBuilder<TextFilterEditorBloc, TextFilterEditorState>(
builder: (blocContext, state) {
return AppFlowyPopover( return AppFlowyPopover(
controller: PopoverController(), controller: PopoverController(),
constraints: BoxConstraints.loose(const Size(200, 76)), constraints: BoxConstraints.loose(const Size(200, 76)),
direction: PopoverDirection.bottomWithCenterAligned, direction: PopoverDirection.bottomWithCenterAligned,
popupBuilder: (BuildContext context) { popupBuilder: (BuildContext context) {
return TextFilterEditor(filterInfo: widget.filterInfo); return TextFilterEditor(bloc: bloc);
}, },
child: ChoiceChipButton( child: ChoiceChipButton(
filterInfo: widget.filterInfo, filterInfo: widget.filterInfo,
onTap: () {}, filterDesc: _makeFilterDesc(state),
), ),
); );
},
),
);
}
String _makeFilterDesc(TextFilterEditorState state) {
String filterDesc = state.filter.condition.choicechipPrefix;
if (state.filter.condition == TextFilterCondition.TextIsEmpty ||
state.filter.condition == TextFilterCondition.TextIsNotEmpty) {
return filterDesc;
}
if (state.filter.content.isNotEmpty) {
filterDesc += " ${state.filter.content}";
}
return filterDesc;
} }
} }
class TextFilterEditor extends StatefulWidget { class TextFilterEditor extends StatefulWidget {
final FilterInfo filterInfo; final TextFilterEditorBloc bloc;
const TextFilterEditor({required this.filterInfo, Key? key}) const TextFilterEditor({required this.bloc, Key? key}) : super(key: key);
: super(key: key);
@override @override
State<TextFilterEditor> createState() => _TextFilterEditorState(); State<TextFilterEditor> createState() => _TextFilterEditorState();
@ -57,20 +92,23 @@ class _TextFilterEditorState extends State<TextFilterEditor> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider.value(
create: (context) => TextFilterEditorBloc(filterInfo: widget.filterInfo) value: widget.bloc,
..add(const TextFilterEditorEvent.initial()),
child: BlocBuilder<TextFilterEditorBloc, TextFilterEditorState>( child: BlocBuilder<TextFilterEditorBloc, TextFilterEditorState>(
builder: (context, state) { builder: (context, state) {
final List<Widget> children = [
_buildFilterPannel(context, state),
];
if (state.filter.condition != TextFilterCondition.TextIsEmpty &&
state.filter.condition != TextFilterCondition.TextIsNotEmpty) {
children.add(const VSpace(4));
children.add(_buildFilterTextField(context, state));
}
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1), padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
child: Column( child: IntrinsicHeight(child: Column(children: children)),
children: [
_buildFilterPannel(context, state),
const VSpace(4),
_buildFilterTextField(context, state),
],
),
); );
}, },
), ),
@ -113,9 +151,8 @@ class _TextFilterEditorState extends State<TextFilterEditor> {
Widget _buildFilterTextField( Widget _buildFilterTextField(
BuildContext context, TextFilterEditorState state) { BuildContext context, TextFilterEditorState state) {
final textFilter = state.filterInfo.textFilter()!;
return FilterTextField( return FilterTextField(
text: textFilter.content, text: state.filter.content,
hintText: LocaleKeys.grid_settings_typeAValue.tr(), hintText: LocaleKeys.grid_settings_typeAValue.tr(),
autoFucous: false, autoFucous: false,
onSubmitted: (text) { onSubmitted: (text) {
@ -209,4 +246,23 @@ extension TextFilterConditionExtension on TextFilterCondition {
return ""; return "";
} }
} }
String get choicechipPrefix {
switch (this) {
case TextFilterCondition.DoesNotContain:
return LocaleKeys.grid_textFilter_choicechipPrefix_isNot.tr();
case TextFilterCondition.EndsWith:
return LocaleKeys.grid_textFilter_choicechipPrefix_endWith.tr();
case TextFilterCondition.IsNot:
return LocaleKeys.grid_textFilter_choicechipPrefix_isNot.tr();
case TextFilterCondition.StartsWith:
return LocaleKeys.grid_textFilter_choicechipPrefix_startWith.tr();
case TextFilterCondition.TextIsEmpty:
return LocaleKeys.grid_textFilter_choicechipPrefix_isEmpty.tr();
case TextFilterCondition.TextIsNotEmpty:
return LocaleKeys.grid_textFilter_choicechipPrefix_isNotEmpty.tr();
default:
return "";
}
}
} }

View file

@ -17,11 +17,13 @@ class GridCreateFilterList extends StatefulWidget {
final String viewId; final String viewId;
final GridFieldController fieldController; final GridFieldController fieldController;
final VoidCallback onClosed; final VoidCallback onClosed;
final VoidCallback? onCreateFilter;
const GridCreateFilterList({ const GridCreateFilterList({
required this.viewId, required this.viewId,
required this.fieldController, required this.fieldController,
required this.onClosed, required this.onClosed,
this.onCreateFilter,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -102,6 +104,7 @@ class _GridCreateFilterListState extends State<GridCreateFilterList> {
void createFilter(FieldInfo field) { void createFilter(FieldInfo field) {
editBloc.add(GridCreateFilterEvent.createDefaultFilter(field)); editBloc.add(GridCreateFilterEvent.createDefaultFilter(field));
widget.onCreateFilter?.call();
} }
} }

View file

@ -13,7 +13,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'create_filter_list.dart'; import 'create_filter_list.dart';
import 'filter_info.dart';
import 'menu_item.dart'; import 'menu_item.dart';
class GridFilterMenu extends StatelessWidget { class GridFilterMenu extends StatelessWidget {
@ -28,7 +27,7 @@ class GridFilterMenu extends StatelessWidget {
children: [ children: [
buildDivider(context), buildDivider(context),
const VSpace(6), const VSpace(6),
buildFilterItems(state.viewId, state.filters), buildFilterItems(state.viewId, state),
], ],
)); ));
} else { } else {
@ -55,8 +54,8 @@ class GridFilterMenu extends StatelessWidget {
); );
} }
Widget buildFilterItems(String viewId, List<FilterInfo> filters) { Widget buildFilterItems(String viewId, GridFilterMenuState state) {
final List<Widget> children = filters final List<Widget> children = state.filters
.map((filterInfo) => FilterMenuItem(filterInfo: filterInfo)) .map((filterInfo) => FilterMenuItem(filterInfo: filterInfo))
.toList(); .toList();
return Row( return Row(
@ -70,7 +69,7 @@ class GridFilterMenu extends StatelessWidget {
), ),
), ),
const HSpace(4), const HSpace(4),
AddFilterButton(viewId: viewId), if (state.creatableFields.isNotEmpty) AddFilterButton(viewId: viewId),
], ],
); );
} }
@ -110,9 +109,7 @@ class _AddFilterButtonState extends State<AddFilterButton> {
"home/add", "home/add",
color: Theme.of(context).colorScheme.onSurface, color: Theme.of(context).colorScheme.onSurface,
), ),
onTap: () { onTap: () => popoverController.show(),
popoverController.show();
},
), ),
), ),
); );

View file

@ -69,6 +69,11 @@ class _FilterButtonState extends State<FilterButton> {
viewId: bloc.viewId, viewId: bloc.viewId,
fieldController: bloc.fieldController, fieldController: bloc.fieldController,
onClosed: () => _popoverController.close(), onClosed: () => _popoverController.close(),
onCreateFilter: () {
if (!bloc.state.isVisible) {
bloc.add(const GridFilterMenuEvent.toggleMenu());
}
},
); );
}, },
); );

View file

@ -63,8 +63,7 @@ class FlowyButton extends StatelessWidget {
children.add(Expanded(child: text)); children.add(Expanded(child: text));
if (rightIcon != null) { if (rightIcon != null) {
children.add( children.add(rightIcon!);
SizedBox.fromSize(size: const Size.square(16), child: rightIcon!));
} }
Widget child = Row( Widget child = Row(

View file

@ -1209,7 +1209,7 @@ packages:
name: textfield_tags name: textfield_tags
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.0+1" version: "2.0.2"
textstyle_extensions: textstyle_extensions:
dependency: "direct main" dependency: "direct main"
description: description:

View file

@ -70,7 +70,7 @@ dependencies:
connectivity_plus: ^2.3.6+1 connectivity_plus: ^2.3.6+1
connectivity_plus_platform_interface: ^1.2.2 connectivity_plus_platform_interface: ^1.2.2
easy_localization: ^3.0.0 easy_localization: ^3.0.0
textfield_tags: ^2.0.0 textfield_tags: ^2.0.2
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.2

View file

@ -24,7 +24,7 @@ void main() {
assert(bloc.state.app.name == 'Hello world'); assert(bloc.state.app.name == 'Hello world');
}); });
test('delete ap test', () async { test('delete app test', () async {
final app = await testContext.createTestApp(); final app = await testContext.createTestApp();
final bloc = AppBloc(app: app)..add(const AppEvent.initial()); final bloc = AppBloc(app: app)..add(const AppEvent.initial());
await blocResponseFuture(); await blocResponseFuture();
@ -64,9 +64,11 @@ void main() {
await blocResponseFuture(); await blocResponseFuture();
bloc.add(AppEvent.createView("3", DocumentPluginBuilder())); bloc.add(AppEvent.createView("3", DocumentPluginBuilder()));
await blocResponseFuture(); await blocResponseFuture();
assert(bloc.state.views.length == 3);
final appViewData = AppViewDataContext(appId: app.id); final appViewData = AppViewDataContext(appId: app.id);
appViewData.views = bloc.state.views; appViewData.views = bloc.state.views;
final viewSectionBloc = ViewSectionBloc( final viewSectionBloc = ViewSectionBloc(
appViewData: appViewData, appViewData: appViewData,
)..add(const ViewSectionEvent.initial()); )..add(const ViewSectionEvent.initial());

View file

@ -39,8 +39,8 @@ impl DocumentMigration {
} }
let document_id = revisions.first().unwrap().object_id.clone(); let document_id = revisions.first().unwrap().object_id.clone();
match make_operations_from_revisions(revisions) { if let Ok(delta) = make_operations_from_revisions(revisions) {
Ok(delta) => match DeltaRevisionMigration::run(delta) { match DeltaRevisionMigration::run(delta) {
Ok(transaction) => { Ok(transaction) => {
let bytes = Bytes::from(transaction.to_bytes()?); let bytes = Bytes::from(transaction.to_bytes()?);
let md5 = format!("{:x}", md5::compute(&bytes)); let md5 = format!("{:x}", md5::compute(&bytes));
@ -59,9 +59,6 @@ impl DocumentMigration {
err err
); );
} }
},
Err(e) => {
tracing::error!("[Document migration]: Make delta from revisions failed: {:?}", e);
} }
} }
} }

View file

@ -212,19 +212,18 @@ impl FilterController {
filter_id = new_filter.as_ref().map(|filter| filter.id.clone()); filter_id = new_filter.as_ref().map(|filter| filter.id.clone());
} }
// Update the cached filter // Update the corresponding filter in the cache
if let Some(filter_rev) = self.delegate.get_filter_rev(updated_filter_type.new.clone()).await { if let Some(filter_rev) = self.delegate.get_filter_rev(updated_filter_type.new.clone()).await {
let _ = self.cache_filters(vec![filter_rev]).await; let _ = self.cache_filters(vec![filter_rev]).await;
} }
if let Some(filter_id) = filter_id { if let Some(filter_id) = filter_id {
let updated_filter = UpdatedFilter {
filter_id,
filter: new_filter,
};
notification = Some(FilterChangesetNotificationPB::from_update( notification = Some(FilterChangesetNotificationPB::from_update(
&self.view_id, &self.view_id,
vec![updated_filter], vec![UpdatedFilter {
filter_id,
filter: new_filter,
}],
)); ));
} }
} }
@ -331,7 +330,7 @@ fn filter_row(
}; };
} }
} }
None Some((row_rev.id.clone(), true))
} }
// Returns None if there is no change in this cell after applying the filter // Returns None if there is no change in this cell after applying the filter

View file

@ -99,7 +99,7 @@ pub fn move_group_row(group: &mut Group, context: &mut MoveGroupRowContext) -> O
inserted_row.index = Some(to_index as i32); inserted_row.index = Some(to_index as i32);
group.insert_row(to_index, row_pb); group.insert_row(to_index, row_pb);
} else { } else {
tracing::warn!("Mote to index: {} is out of bounds", to_index); tracing::warn!("Move to index: {} is out of bounds", to_index);
tracing::debug!("Group:{} append row:{}", group.id, row_rev.id); tracing::debug!("Group:{} append row:{}", group.id, row_rev.id);
group.add_row(row_pb); group.add_row(row_pb);
} }

View file

@ -359,6 +359,7 @@ impl GridViewRevisionEditor {
.await .await
.did_receive_filter_changed(FilterChangeset::from_delete(filter_type.clone())) .did_receive_filter_changed(FilterChangeset::from_delete(filter_type.clone()))
.await; .await;
let _ = self let _ = self
.modify(|pad| { .modify(|pad| {
let changeset = pad.delete_filter(&params.filter_id, &filter_type.field_id, &field_type_rev)?; let changeset = pad.delete_filter(&params.filter_id, &filter_type.field_id, &field_type_rev)?;

View file

@ -206,8 +206,8 @@ impl GridFilterTest {
let mut receiver = self.editor.subscribe_view_changed(&self.grid_id).await.unwrap(); let mut receiver = self.editor.subscribe_view_changed(&self.grid_id).await.unwrap();
match tokio::time::timeout(Duration::from_secs(2), receiver.recv()).await { match tokio::time::timeout(Duration::from_secs(2), receiver.recv()).await {
Ok(changed) => match changed.unwrap() { GridViewChanged::DidReceiveFilterResult(changed) => { Ok(changed) => match changed.unwrap() { GridViewChanged::DidReceiveFilterResult(changed) => {
assert_eq!(changed.visible_rows.len(), visible_row_len); assert_eq!(changed.visible_rows.len(), visible_row_len, "visible rows not match");
assert_eq!(changed.invisible_rows.len(), hide_row_len); assert_eq!(changed.invisible_rows.len(), hide_row_len, "invisible rows not match");
} }, } },
Err(e) => { Err(e) => {
panic!("Process task timeout: {:?}", e); panic!("Process task timeout: {:?}", e);

View file

@ -98,20 +98,16 @@ async fn grid_filter_single_select_is_test2() {
row_index: 1, row_index: 1,
option_id: option.id.clone(), option_id: option.id.clone(),
}, },
AssertFilterChanged {
visible_row_len: 1,
hide_row_len: 0,
},
AssertNumberOfVisibleRows { expected: 3 }, AssertNumberOfVisibleRows { expected: 3 },
UpdateSingleSelectCell { UpdateSingleSelectCell {
row_index: 1, row_index: 1,
option_id: "".to_string(), option_id: "".to_string(),
}, },
// AssertFilterChanged { AssertFilterChanged {
// visible_row_len: 0, visible_row_len: 0,
// hide_row_len: 1, hide_row_len: 1,
// }, },
// AssertNumberOfVisibleRows { expected: 2 }, AssertNumberOfVisibleRows { expected: 2 },
]; ];
test.run_scripts(scripts).await; test.run_scripts(scripts).await;
} }