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> {
final PageController _pageController = PageController();
final pageController = PageController();
final scrollController = ScrollController();
@override
void dispose() {
_pageController.dispose();
pageController.dispose();
scrollController.dispose();
super.dispose();
}
@ -47,7 +49,7 @@ class _MobileFilterEditorState extends State<MobileFilterEditor> {
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => MobileFilterEditorCubit(
pageController: _pageController,
pageController: pageController,
),
child: Column(
children: [
@ -55,12 +57,12 @@ class _MobileFilterEditorState extends State<MobileFilterEditor> {
SizedBox(
height: 400,
child: PageView.builder(
controller: _pageController,
controller: pageController,
itemCount: 2,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return switch (index) {
0 => const _ActiveFilters(),
0 => _ActiveFilters(scrollController: scrollController),
1 => const _FilterDetail(),
_ => const SizedBox.shrink(),
};
@ -124,15 +126,13 @@ class _Header extends StatelessWidget {
bool _isBackButtonShown(MobileFilterEditorState state) {
return state.maybeWhen(
overview: () => false,
overview: (_) => false,
orElse: () => true,
);
}
bool _isSaveButtonShown(MobileFilterEditorState state) {
return state.maybeWhen(
create: (_) => true,
editField: (_, __) => true,
editCondition: (filterId, newFilter, showSave) => showSave,
editContent: (_, __) => true,
orElse: () => false,
@ -141,8 +141,6 @@ class _Header extends StatelessWidget {
bool _isSaveButtonEnabled(MobileFilterEditorState state) {
return state.maybeWhen(
create: (field) => field != null,
editField: (_, __) => true,
editCondition: (_, __, enableSave) => enableSave,
editContent: (_, __) => true,
orElse: () => false,
@ -151,27 +149,6 @@ class _Header extends StatelessWidget {
void _saveOnTapHandler(BuildContext context, MobileFilterEditorState state) {
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, _) {
context
.read<FilterEditorBloc>()
@ -189,7 +166,11 @@ class _Header extends StatelessWidget {
}
class _ActiveFilters extends StatelessWidget {
const _ActiveFilters();
const _ActiveFilters({
required this.scrollController,
});
final ScrollController scrollController;
@override
Widget build(BuildContext context) {
@ -235,7 +216,21 @@ class _ActiveFilters extends StatelessWidget {
}
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(
controller: scrollController,
padding: const EdgeInsets.symmetric(
horizontal: 16,
),
@ -548,16 +543,32 @@ class _FilterDetail extends StatelessWidget {
return BlocBuilder<MobileFilterEditorCubit, MobileFilterEditorState>(
builder: (context, state) {
return state.maybeWhen(
create: (filterField) {
return _FilterableFieldList(
onSelectField: (field) =>
context.read<MobileFilterEditorCubit>().changeField(field),
);
},
editField: (filterId, newField) {
create: () {
return _FilterableFieldList(
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(
itemCount: blocState.fields.length,
itemBuilder: (context, index) {
return FlowyOptionTile.checkbox(
return FlowyOptionTile.text(
text: blocState.fields[index].name,
leftIcon: FieldIcon(
fieldInfo: blocState.fields[index],
),
showTopBorder: false,
isSelected: _isSelected(context, blocState, 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 {
@ -960,6 +947,14 @@ class _SelectOptionFilterContentEditor extends StatefulWidget {
class _SelectOptionFilterContentEditorState
extends State<_SelectOptionFilterContentEditor> {
final TextEditingController textController = TextEditingController();
String filterText = "";
final List<SelectOptionPB> options = [];
@override
void initState() {
super.initState();
options.addAll(widget.delegate.getOptions(widget.field));
}
@override
void dispose() {
@ -969,7 +964,6 @@ class _SelectOptionFilterContentEditorState
@override
Widget build(BuildContext context) {
final options = widget.delegate.getOptions(widget.field);
return Column(
children: [
const Divider(
@ -980,8 +974,15 @@ class _SelectOptionFilterContentEditorState
padding: const EdgeInsets.all(16.0),
child: FlowyMobileSearchTextField(
controller: textController,
onChanged: (asdf) {},
onSubmitted: (asdf) {},
onChanged: (text) {
if (textController.value.composing.isCollapsed) {
setState(() {
filterText = text;
filterOptions();
});
}
},
onSubmitted: (_) {},
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() {
return (widget.filter.condition == SelectOptionFilterConditionPB.OptionIs ||
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:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -13,34 +12,19 @@ class MobileFilterEditorCubit extends Cubit<MobileFilterEditorState> {
final PageController pageController;
void returnToOverview() {
void returnToOverview({bool scrollToBottom = false}) {
_animateToPage(0);
emit(MobileFilterEditorState.overview());
emit(MobileFilterEditorState.overview(scrollToBottom: scrollToBottom));
}
void startCreatingFilter() {
_animateToPage(1);
emit(MobileFilterEditorState.create(filterField: null));
emit(MobileFilterEditorState.create());
}
void startEditingFilterField(String filterId) {
_animateToPage(1);
emit(MobileFilterEditorState.editField(filterId: filterId, newField: null));
}
void changeField(FieldInfo field) {
emit(
state.maybeWhen(
create: (_) => MobileFilterEditorState.create(
filterField: field,
),
editField: (filterId, _) => MobileFilterEditorState.editField(
filterId: filterId,
newField: field,
),
orElse: () => state,
),
);
emit(MobileFilterEditorState.editField(filterId: filterId));
}
void updateFilter(DatabaseFilter filter) {
@ -97,15 +81,14 @@ class MobileFilterEditorCubit extends Cubit<MobileFilterEditorState> {
@freezed
class MobileFilterEditorState with _$MobileFilterEditorState {
factory MobileFilterEditorState.overview() = _OverviewState;
factory MobileFilterEditorState.overview({
@Default(false) bool scrollToBottom,
}) = _OverviewState;
factory MobileFilterEditorState.create({
required FieldInfo? filterField,
}) = _CreateState;
factory MobileFilterEditorState.create() = _CreateState;
factory MobileFilterEditorState.editField({
required String filterId,
required FieldInfo? newField,
}) = _EditFieldState;
factory MobileFilterEditorState.editCondition({

View File

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

View File

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