diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_filter_bottom_sheet.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_filter_bottom_sheet.dart index 29fee8ec34..e63fa8da57 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_filter_bottom_sheet.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_filter_bottom_sheet.dart @@ -35,11 +35,13 @@ class MobileFilterEditor extends StatefulWidget { } class _MobileFilterEditorState extends State { - 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 { Widget build(BuildContext context) { return BlocProvider( create: (context) => MobileFilterEditorCubit( - pageController: _pageController, + pageController: pageController, ), child: Column( children: [ @@ -55,12 +57,12 @@ class _MobileFilterEditorState extends State { 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() - .add(FilterEditorEvent.createFilter(filterField)); - } - }, - editField: (filterId, newField) { - final filter = context - .read() - .state - .filters - .firstWhereOrNull((filter) => filter.filterId == filterId); - if (newField != null && - filter != null && - newField.id != filter.fieldId) { - context - .read() - .add(FilterEditorEvent.changeFilteringField(filterId, newField)); - } - }, editCondition: (filterId, newFilter, _) { context .read() @@ -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().state.maybeWhen( + overview: (scrollToBottom) { + if (scrollToBottom && scrollController.hasClients) { + scrollController + .jumpTo(scrollController.position.maxScrollExtent); + context.read().returnToOverview(); + } + }, + orElse: () {}, + ); + }); + return ListView.separated( + controller: scrollController, padding: const EdgeInsets.symmetric( horizontal: 16, ), @@ -548,16 +543,32 @@ class _FilterDetail extends StatelessWidget { return BlocBuilder( builder: (context, state) { return state.maybeWhen( - create: (filterField) { - return _FilterableFieldList( - onSelectField: (field) => - context.read().changeField(field), - ); - }, - editField: (filterId, newField) { + create: () { return _FilterableFieldList( onSelectField: (field) { - context.read().changeField(field); + context + .read() + .add(FilterEditorEvent.createFilter(field)); + context.read().returnToOverview( + scrollToBottom: true, + ); + }, + ); + }, + editField: (filterId) { + return _FilterableFieldList( + onSelectField: (field) { + final filter = context + .read() + .state + .filters + .firstWhereOrNull((filter) => filter.filterId == filterId); + if (filter != null && field.id != filter.fieldId) { + context.read().add( + FilterEditorEvent.changeFilteringField(filterId, field), + ); + } + context.read().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().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 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 == diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_filter_bottom_sheet_cubit.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_filter_bottom_sheet_cubit.dart index 44380ad76a..a62ef846ae 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_filter_bottom_sheet_cubit.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_filter_bottom_sheet_cubit.dart @@ -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 { 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 { @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({ diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/field/field_editor.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/field/field_editor.dart index 63c52f46ff..7e26b34765 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/field/field_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/field/field_editor.dart @@ -570,7 +570,7 @@ class _FieldEditIconButtonState extends State { 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 { Widget build(BuildContext context) { return BlocBuilder( 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 { 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, ), ), ), diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/space_icon_popup.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/space_icon_popup.dart index fc9e462e9b..419e191857 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/space_icon_popup.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/space_icon_popup.dart @@ -74,7 +74,7 @@ class _SpaceIconPopupState extends State { 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(), diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 8d67209e6c..21928cff7c 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -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",