fix: launch review issues (#6479)

* fix(flutter_desktop): field icon picker popover constraints

* fix(mobile): immediately select field when editing filter

* fix: filter through select options not working

* fix(mobile): scroll to bottom after filter creation

* fix: primary field tooltip
This commit is contained in:
Richard Shiue 2024-10-05 10:57:52 +08:00 committed by GitHub
parent 8ceff03f3e
commit 8e6f051dec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 123 additions and 106 deletions

View File

@ -35,11 +35,13 @@ class MobileFilterEditor extends StatefulWidget {
} }
class _MobileFilterEditorState extends State<MobileFilterEditor> { class _MobileFilterEditorState extends State<MobileFilterEditor> {
final PageController _pageController = PageController(); final pageController = PageController();
final scrollController = ScrollController();
@override @override
void dispose() { void dispose() {
_pageController.dispose(); pageController.dispose();
scrollController.dispose();
super.dispose(); super.dispose();
} }
@ -47,7 +49,7 @@ class _MobileFilterEditorState extends State<MobileFilterEditor> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => MobileFilterEditorCubit( create: (context) => MobileFilterEditorCubit(
pageController: _pageController, pageController: pageController,
), ),
child: Column( child: Column(
children: [ children: [
@ -55,12 +57,12 @@ class _MobileFilterEditorState extends State<MobileFilterEditor> {
SizedBox( SizedBox(
height: 400, height: 400,
child: PageView.builder( child: PageView.builder(
controller: _pageController, controller: pageController,
itemCount: 2, itemCount: 2,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) { itemBuilder: (context, index) {
return switch (index) { return switch (index) {
0 => const _ActiveFilters(), 0 => _ActiveFilters(scrollController: scrollController),
1 => const _FilterDetail(), 1 => const _FilterDetail(),
_ => const SizedBox.shrink(), _ => const SizedBox.shrink(),
}; };
@ -124,15 +126,13 @@ class _Header extends StatelessWidget {
bool _isBackButtonShown(MobileFilterEditorState state) { bool _isBackButtonShown(MobileFilterEditorState state) {
return state.maybeWhen( return state.maybeWhen(
overview: () => false, overview: (_) => false,
orElse: () => true, orElse: () => true,
); );
} }
bool _isSaveButtonShown(MobileFilterEditorState state) { bool _isSaveButtonShown(MobileFilterEditorState state) {
return state.maybeWhen( return state.maybeWhen(
create: (_) => true,
editField: (_, __) => true,
editCondition: (filterId, newFilter, showSave) => showSave, editCondition: (filterId, newFilter, showSave) => showSave,
editContent: (_, __) => true, editContent: (_, __) => true,
orElse: () => false, orElse: () => false,
@ -141,8 +141,6 @@ class _Header extends StatelessWidget {
bool _isSaveButtonEnabled(MobileFilterEditorState state) { bool _isSaveButtonEnabled(MobileFilterEditorState state) {
return state.maybeWhen( return state.maybeWhen(
create: (field) => field != null,
editField: (_, __) => true,
editCondition: (_, __, enableSave) => enableSave, editCondition: (_, __, enableSave) => enableSave,
editContent: (_, __) => true, editContent: (_, __) => true,
orElse: () => false, orElse: () => false,
@ -151,27 +149,6 @@ class _Header extends StatelessWidget {
void _saveOnTapHandler(BuildContext context, MobileFilterEditorState state) { void _saveOnTapHandler(BuildContext context, MobileFilterEditorState state) {
state.maybeWhen( state.maybeWhen(
create: (filterField) {
if (filterField != null) {
context
.read<FilterEditorBloc>()
.add(FilterEditorEvent.createFilter(filterField));
}
},
editField: (filterId, newField) {
final filter = context
.read<FilterEditorBloc>()
.state
.filters
.firstWhereOrNull((filter) => filter.filterId == filterId);
if (newField != null &&
filter != null &&
newField.id != filter.fieldId) {
context
.read<FilterEditorBloc>()
.add(FilterEditorEvent.changeFilteringField(filterId, newField));
}
},
editCondition: (filterId, newFilter, _) { editCondition: (filterId, newFilter, _) {
context context
.read<FilterEditorBloc>() .read<FilterEditorBloc>()
@ -189,7 +166,11 @@ class _Header extends StatelessWidget {
} }
class _ActiveFilters extends StatelessWidget { class _ActiveFilters extends StatelessWidget {
const _ActiveFilters(); const _ActiveFilters({
required this.scrollController,
});
final ScrollController scrollController;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -235,7 +216,21 @@ class _ActiveFilters extends StatelessWidget {
} }
Widget _filterList(BuildContext context, FilterEditorState state) { Widget _filterList(BuildContext context, FilterEditorState state) {
WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<MobileFilterEditorCubit>().state.maybeWhen(
overview: (scrollToBottom) {
if (scrollToBottom && scrollController.hasClients) {
scrollController
.jumpTo(scrollController.position.maxScrollExtent);
context.read<MobileFilterEditorCubit>().returnToOverview();
}
},
orElse: () {},
);
});
return ListView.separated( return ListView.separated(
controller: scrollController,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 16, horizontal: 16,
), ),
@ -548,16 +543,32 @@ class _FilterDetail extends StatelessWidget {
return BlocBuilder<MobileFilterEditorCubit, MobileFilterEditorState>( return BlocBuilder<MobileFilterEditorCubit, MobileFilterEditorState>(
builder: (context, state) { builder: (context, state) {
return state.maybeWhen( return state.maybeWhen(
create: (filterField) { create: () {
return _FilterableFieldList(
onSelectField: (field) =>
context.read<MobileFilterEditorCubit>().changeField(field),
);
},
editField: (filterId, newField) {
return _FilterableFieldList( return _FilterableFieldList(
onSelectField: (field) { onSelectField: (field) {
context.read<MobileFilterEditorCubit>().changeField(field); context
.read<FilterEditorBloc>()
.add(FilterEditorEvent.createFilter(field));
context.read<MobileFilterEditorCubit>().returnToOverview(
scrollToBottom: true,
);
},
);
},
editField: (filterId) {
return _FilterableFieldList(
onSelectField: (field) {
final filter = context
.read<FilterEditorBloc>()
.state
.filters
.firstWhereOrNull((filter) => filter.filterId == filterId);
if (filter != null && field.id != filter.fieldId) {
context.read<FilterEditorBloc>().add(
FilterEditorEvent.changeFilteringField(filterId, field),
);
}
context.read<MobileFilterEditorCubit>().returnToOverview();
}, },
); );
}, },
@ -619,13 +630,12 @@ class _FilterableFieldList extends StatelessWidget {
return ListView.builder( return ListView.builder(
itemCount: blocState.fields.length, itemCount: blocState.fields.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return FlowyOptionTile.checkbox( return FlowyOptionTile.text(
text: blocState.fields[index].name, text: blocState.fields[index].name,
leftIcon: FieldIcon( leftIcon: FieldIcon(
fieldInfo: blocState.fields[index], fieldInfo: blocState.fields[index],
), ),
showTopBorder: false, showTopBorder: false,
isSelected: _isSelected(context, blocState, index),
onTap: () => onSelectField(blocState.fields[index]), onTap: () => onSelectField(blocState.fields[index]),
); );
}, },
@ -636,29 +646,6 @@ class _FilterableFieldList extends StatelessWidget {
], ],
); );
} }
bool _isSelected(BuildContext context, FilterEditorState state, int index) {
final field = state.fields[index];
return context.watch<MobileFilterEditorCubit>().state.maybeWhen(
create: (selectedField) {
return selectedField != null && selectedField.id == field.id;
},
editField: (filterId, selectedField) {
final filter = state.filters.firstWhereOrNull(
(filter) => filter.filterId == filterId,
);
final isOriginalSelectedField =
selectedField == null && filter?.fieldId == field.id;
final isNewSelectedField =
selectedField != null && selectedField.id == field.id;
return isOriginalSelectedField || isNewSelectedField;
},
orElse: () => false,
);
}
} }
class _FilterConditionList extends StatelessWidget { class _FilterConditionList extends StatelessWidget {
@ -960,6 +947,14 @@ class _SelectOptionFilterContentEditor extends StatefulWidget {
class _SelectOptionFilterContentEditorState class _SelectOptionFilterContentEditorState
extends State<_SelectOptionFilterContentEditor> { extends State<_SelectOptionFilterContentEditor> {
final TextEditingController textController = TextEditingController(); final TextEditingController textController = TextEditingController();
String filterText = "";
final List<SelectOptionPB> options = [];
@override
void initState() {
super.initState();
options.addAll(widget.delegate.getOptions(widget.field));
}
@override @override
void dispose() { void dispose() {
@ -969,7 +964,6 @@ class _SelectOptionFilterContentEditorState
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final options = widget.delegate.getOptions(widget.field);
return Column( return Column(
children: [ children: [
const Divider( const Divider(
@ -980,8 +974,15 @@ class _SelectOptionFilterContentEditorState
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: FlowyMobileSearchTextField( child: FlowyMobileSearchTextField(
controller: textController, controller: textController,
onChanged: (asdf) {}, onChanged: (text) {
onSubmitted: (asdf) {}, if (textController.value.composing.isCollapsed) {
setState(() {
filterText = text;
filterOptions();
});
}
},
onSubmitted: (_) {},
hintText: LocaleKeys.grid_selectOption_searchOption.tr(), hintText: LocaleKeys.grid_selectOption_searchOption.tr(),
), ),
), ),
@ -1012,6 +1013,20 @@ class _SelectOptionFilterContentEditorState
); );
} }
void filterOptions() {
options
..clear()
..addAll(widget.delegate.getOptions(widget.field));
if (filterText.isNotEmpty) {
options.retainWhere((option) {
final name = option.name.toLowerCase();
final lFilter = filterText.toLowerCase();
return name.contains(lFilter);
});
}
}
MobileSelectedOptionIndicator _getIndicator() { MobileSelectedOptionIndicator _getIndicator() {
return (widget.filter.condition == SelectOptionFilterConditionPB.OptionIs || return (widget.filter.condition == SelectOptionFilterConditionPB.OptionIs ||
widget.filter.condition == widget.filter.condition ==

View File

@ -1,4 +1,3 @@
import 'package:appflowy/plugins/database/application/field/field_info.dart';
import 'package:appflowy/plugins/database/application/field/filter_entities.dart'; import 'package:appflowy/plugins/database/application/field/filter_entities.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -13,34 +12,19 @@ class MobileFilterEditorCubit extends Cubit<MobileFilterEditorState> {
final PageController pageController; final PageController pageController;
void returnToOverview() { void returnToOverview({bool scrollToBottom = false}) {
_animateToPage(0); _animateToPage(0);
emit(MobileFilterEditorState.overview()); emit(MobileFilterEditorState.overview(scrollToBottom: scrollToBottom));
} }
void startCreatingFilter() { void startCreatingFilter() {
_animateToPage(1); _animateToPage(1);
emit(MobileFilterEditorState.create(filterField: null)); emit(MobileFilterEditorState.create());
} }
void startEditingFilterField(String filterId) { void startEditingFilterField(String filterId) {
_animateToPage(1); _animateToPage(1);
emit(MobileFilterEditorState.editField(filterId: filterId, newField: null)); emit(MobileFilterEditorState.editField(filterId: filterId));
}
void changeField(FieldInfo field) {
emit(
state.maybeWhen(
create: (_) => MobileFilterEditorState.create(
filterField: field,
),
editField: (filterId, _) => MobileFilterEditorState.editField(
filterId: filterId,
newField: field,
),
orElse: () => state,
),
);
} }
void updateFilter(DatabaseFilter filter) { void updateFilter(DatabaseFilter filter) {
@ -97,15 +81,14 @@ class MobileFilterEditorCubit extends Cubit<MobileFilterEditorState> {
@freezed @freezed
class MobileFilterEditorState with _$MobileFilterEditorState { class MobileFilterEditorState with _$MobileFilterEditorState {
factory MobileFilterEditorState.overview() = _OverviewState; factory MobileFilterEditorState.overview({
@Default(false) bool scrollToBottom,
}) = _OverviewState;
factory MobileFilterEditorState.create({ factory MobileFilterEditorState.create() = _CreateState;
required FieldInfo? filterField,
}) = _CreateState;
factory MobileFilterEditorState.editField({ factory MobileFilterEditorState.editField({
required String filterId, required String filterId,
required FieldInfo? newField,
}) = _EditFieldState; }) = _EditFieldState;
factory MobileFilterEditorState.editCondition({ factory MobileFilterEditorState.editCondition({

View File

@ -570,7 +570,7 @@ class _FieldEditIconButtonState extends State<FieldEditIconButton> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AppFlowyPopover( return AppFlowyPopover(
offset: const Offset(0, 4), offset: const Offset(0, 4),
constraints: BoxConstraints.loose(const Size(380, 432)), constraints: BoxConstraints.loose(const Size(360, 432)),
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
direction: PopoverDirection.bottomWithLeftAligned, direction: PopoverDirection.bottomWithLeftAligned,
controller: popoverController, controller: popoverController,
@ -693,12 +693,37 @@ class _SwitchFieldButtonState extends State<SwitchFieldButton> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<FieldEditorBloc, FieldEditorState>( return BlocBuilder<FieldEditorBloc, FieldEditorState>(
builder: (context, state) { builder: (context, state) {
final bool isPrimary = state.field.isPrimary; if (state.field.isPrimary) {
return SizedBox(
height: GridSize.popoverItemHeight,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: FlowyTooltip(
message: LocaleKeys.grid_field_switchPrimaryFieldTooltip.tr(),
child: FlowyButton(
text: FlowyText(
state.field.fieldType.i18n,
lineHeight: 1.0,
color: Theme.of(context).disabledColor,
),
leftIcon: FlowySvg(
state.field.fieldType.svgData,
color: Theme.of(context).disabledColor,
),
rightIcon: FlowySvg(
FlowySvgs.more_s,
color: Theme.of(context).disabledColor,
),
),
),
),
);
}
return SizedBox( return SizedBox(
height: GridSize.popoverItemHeight, height: GridSize.popoverItemHeight,
child: AppFlowyPopover( child: AppFlowyPopover(
constraints: BoxConstraints.loose(const Size(460, 540)), constraints: BoxConstraints.loose(const Size(460, 540)),
triggerActions: isPrimary ? 0 : PopoverTriggerFlags.hover, triggerActions: PopoverTriggerFlags.hover,
mutex: widget.popoverMutex, mutex: widget.popoverMutex,
controller: _popoverController, controller: _popoverController,
offset: const Offset(8, 0), offset: const Offset(8, 0),
@ -715,23 +740,16 @@ class _SwitchFieldButtonState extends State<SwitchFieldButton> {
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0), padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: FlowyButton( child: FlowyButton(
onTap: () { onTap: () => _popoverController.show(),
if (!isPrimary) {
_popoverController.show();
}
},
text: FlowyText( text: FlowyText(
state.field.fieldType.i18n, state.field.fieldType.i18n,
lineHeight: 1.0, lineHeight: 1.0,
color: isPrimary ? Theme.of(context).disabledColor : null,
), ),
leftIcon: FlowySvg( leftIcon: FlowySvg(
state.field.fieldType.svgData, state.field.fieldType.svgData,
color: isPrimary ? Theme.of(context).disabledColor : null,
), ),
rightIcon: FlowySvg( rightIcon: const FlowySvg(
FlowySvgs.more_s, FlowySvgs.more_s,
color: isPrimary ? Theme.of(context).disabledColor : null,
), ),
), ),
), ),

View File

@ -74,7 +74,7 @@ class _SpaceIconPopupState extends State<SpaceIconPopup> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AppFlowyPopover( return AppFlowyPopover(
offset: const Offset(0, 4), offset: const Offset(0, 4),
constraints: BoxConstraints.loose(const Size(380, 432)), constraints: BoxConstraints.loose(const Size(360, 432)),
margin: const EdgeInsets.all(0), margin: const EdgeInsets.all(0),
direction: PopoverDirection.bottomWithCenterAligned, direction: PopoverDirection.bottomWithCenterAligned,
child: _buildPreview(), child: _buildPreview(),

View File

@ -1338,6 +1338,7 @@
"delete": "Delete", "delete": "Delete",
"wrapCellContent": "Wrap text", "wrapCellContent": "Wrap text",
"clear": "Clear cells", "clear": "Clear cells",
"switchPrimaryFieldTooltip": "Cannot change field type of primary field",
"textFieldName": "Text", "textFieldName": "Text",
"checkboxFieldName": "Checkbox", "checkboxFieldName": "Checkbox",
"dateFieldName": "Date", "dateFieldName": "Date",