From 8229371f6357d353ecdf073de13aa6bdf5404def Mon Sep 17 00:00:00 2001 From: Vincent Chan Date: Wed, 31 Aug 2022 19:15:20 +0800 Subject: [PATCH] feat: add mutex to property list --- .../select_option_editor.dart | 129 +++++++++--------- .../widgets/header/field_cell.dart | 1 + .../widgets/header/field_editor.dart | 1 - .../widgets/header/grid_header.dart | 32 +++-- .../widgets/header/type_option/date.dart | 3 +- .../header/type_option/multi_select.dart | 6 +- .../widgets/header/type_option/number.dart | 3 +- .../header/type_option/select_option.dart | 92 ++++++++----- .../header/type_option/single_select.dart | 6 +- .../widgets/toolbar/grid_property.dart | 37 ++++- .../appflowy_popover/lib/popover.dart | 16 ++- 11 files changed, 198 insertions(+), 128 deletions(-) diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart index 241ee9d309..ef074ed7bb 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart @@ -1,6 +1,7 @@ import 'dart:collection'; import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart'; import 'package:app_flowy/plugins/grid/application/cell/select_option_editor_bloc.dart'; +import 'package:appflowy_popover/popover.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/theme.dart'; @@ -228,76 +229,82 @@ class _CreateOptionCell extends StatelessWidget { } } -class _SelectOptionCell extends StatelessWidget { +class _SelectOptionCell extends StatefulWidget { final SelectOptionPB option; final bool isSelected; const _SelectOptionCell(this.option, this.isSelected, {Key? key}) : super(key: key); @override - Widget build(BuildContext context) { - final theme = context.watch(); - return SizedBox( - height: GridSize.typeOptionItemHeight, - child: Row( - children: [ - Flexible( - fit: FlexFit.loose, - child: SelectOptionTagCell( - option: option, - onSelected: (option) { - context - .read() - .add(SelectOptionEditorEvent.selectOption(option.id)); - }, - children: [ - if (isSelected) - Padding( - padding: const EdgeInsets.only(right: 6), - child: svgWidget("grid/checkmark"), - ), - ], - ), - ), - FlowyIconButton( - width: 30, - onPressed: () => _showEditPannel(context), - iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4), - icon: svgWidget("editor/details", color: theme.iconColor), - ) - ], - ), - ); + State<_SelectOptionCell> createState() => _SelectOptionCellState(); +} + +class _SelectOptionCellState extends State<_SelectOptionCell> { + late PopoverController _popoverController; + + @override + void initState() { + _popoverController = PopoverController(); + super.initState(); } - void _showEditPannel(BuildContext context) { - final pannel = SelectOptionTypeOptionEditor( - option: option, - onDeleted: () { - context - .read() - .add(SelectOptionEditorEvent.deleteOption(option)); - }, - onUpdated: (updatedOption) { - context - .read() - .add(SelectOptionEditorEvent.updateOption(updatedOption)); - }, - key: ValueKey(option - .id), // Use ValueKey to refresh the UI, otherwise, it will remain the old value. - ); - final overlayIdentifier = (SelectOptionTypeOptionEditor).toString(); - - FlowyOverlay.of(context).remove(overlayIdentifier); - FlowyOverlay.of(context).insertWithAnchor( - widget: OverlayContainer( - child: pannel, - constraints: BoxConstraints.loose(const Size(200, 300)), + @override + Widget build(BuildContext context) { + final theme = context.watch(); + return Popover( + controller: _popoverController, + offset: const Offset(20, 0), + child: SizedBox( + height: GridSize.typeOptionItemHeight, + child: Row( + children: [ + Flexible( + fit: FlexFit.loose, + child: SelectOptionTagCell( + option: widget.option, + onSelected: (option) { + context + .read() + .add(SelectOptionEditorEvent.selectOption(option.id)); + }, + children: [ + if (widget.isSelected) + Padding( + padding: const EdgeInsets.only(right: 6), + child: svgWidget("grid/checkmark"), + ), + ], + ), + ), + FlowyIconButton( + width: 30, + onPressed: () => _popoverController.show(), + iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4), + icon: svgWidget("editor/details", color: theme.iconColor), + ) + ], + ), ), - identifier: overlayIdentifier, - anchorContext: context, - anchorDirection: AnchorDirection.rightWithCenterAligned, - anchorOffset: Offset(2 * overlayContainerPadding.left, 0), + popupBuilder: (BuildContext popoverContext) { + return OverlayContainer( + constraints: BoxConstraints.loose(const Size(200, 300)), + child: SelectOptionTypeOptionEditor( + option: widget.option, + onDeleted: () { + context + .read() + .add(SelectOptionEditorEvent.deleteOption(widget.option)); + }, + onUpdated: (updatedOption) { + context + .read() + .add(SelectOptionEditorEvent.updateOption(updatedOption)); + }, + key: ValueKey(widget.option + .id), // Use ValueKey to refresh the UI, otherwise, it will remain the old value. + ), + ); + }, ); } } diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_cell.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_cell.dart index abdbbdb3ab..af912e1231 100755 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_cell.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_cell.dart @@ -35,6 +35,7 @@ class _GridFieldCellState extends State { builder: (context, state) { final button = Popover( controller: popover, + direction: PopoverDirection.bottomWithLeftAligned, child: FieldCellButton( field: state.field, onTap: () => popover.show(), diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_editor.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_editor.dart index 7e40ea1ea7..0abb26022b 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_editor.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_editor.dart @@ -2,7 +2,6 @@ import 'package:app_flowy/plugins/grid/application/field/field_editor_bloc.dart' import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart'; import 'package:appflowy_popover/popover.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flutter/material.dart'; diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/grid_header.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/grid_header.dart index 3b7410f61d..eae09ceefa 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/grid_header.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/grid_header.dart @@ -2,8 +2,10 @@ import 'package:app_flowy/plugins/grid/application/field/field_cache.dart'; import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart'; import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/plugins/grid/application/prelude.dart'; +import 'package:appflowy_popover/popover.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui_web.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; @@ -154,19 +156,25 @@ class CreateFieldButton extends StatelessWidget { Widget build(BuildContext context) { final theme = context.watch(); - return FlowyButton( - text: const FlowyText.medium('New column', fontSize: 12), - hoverColor: theme.shader6, - onTap: () { - // FieldEditorPopOver.show( - // context, - // anchorContext: context, - // gridId: gridId, - // fieldName: "", - // typeOptionLoader: NewFieldTypeOptionLoader(gridId: gridId), - // ) + return Popover( + triggerActions: PopoverTriggerActionFlags.click, + direction: PopoverDirection.bottomWithRightAligned, + child: FlowyButton( + text: const FlowyText.medium('New column', fontSize: 12), + hoverColor: theme.shader6, + onTap: () {}, + leftIcon: svgWidget("home/add"), + ), + popupBuilder: (BuildContext popover) { + return OverlayContainer( + constraints: BoxConstraints.loose(const Size(240, 200)), + child: FieldEditor( + gridId: gridId, + fieldName: "", + typeOptionLoader: NewFieldTypeOptionLoader(gridId: gridId), + ), + ); }, - leftIcon: svgWidget("home/add"), ); } } diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/date.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/date.dart index bd1c82a254..f061ce726f 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/date.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/date.dart @@ -77,6 +77,7 @@ class DateTypeOptionWidget extends TypeOptionWidget { context .read() .add(DateTypeOptionEvent.didSelectDateFormat(format)); + PopoverContainerState.of(popoverContext).closeAll(); }, ), ); @@ -100,7 +101,7 @@ class DateTypeOptionWidget extends TypeOptionWidget { context .read() .add(DateTypeOptionEvent.didSelectTimeFormat(format)); - PopoverContainerState.of(popoverContext).close(); + PopoverContainerState.of(popoverContext).closeAll(); }), ); }, diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/multi_select.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/multi_select.dart index ab8a628146..127bd1f68e 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/multi_select.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/multi_select.dart @@ -1,6 +1,7 @@ import 'package:app_flowy/plugins/grid/application/field/type_option/multi_select_type_option.dart'; import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart'; import 'package:flutter/material.dart'; +import 'package:appflowy_popover/popover.dart'; import '../field_type_option_editor.dart'; import 'builder.dart'; @@ -39,7 +40,10 @@ class MultiSelectTypeOptionWidget extends TypeOptionWidget { Widget build(BuildContext context) { return SelectOptionTypeOptionWidget( options: selectOptionAction.typeOption.options, - beginEdit: () => overlayDelegate.hideOverlay(context), + beginEdit: () { + overlayDelegate.hideOverlay(context); + PopoverContainerState.of(context).closeAll(); + }, overlayDelegate: overlayDelegate, typeOptionAction: selectOptionAction, // key: ValueKey(state.typeOption.hashCode), diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/number.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/number.dart index d9df038d22..c61a2e417f 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/number.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/number.dart @@ -79,7 +79,7 @@ class NumberTypeOptionWidget extends TypeOptionWidget { ], ), ), - popupBuilder: (BuildContext context) { + popupBuilder: (BuildContext popoverContext) { return OverlayContainer( constraints: BoxConstraints.loose(const Size(460, 440)), child: NumberFormatList( @@ -87,6 +87,7 @@ class NumberTypeOptionWidget extends TypeOptionWidget { context .read() .add(NumberTypeOptionEvent.didSelectFormat(format)); + PopoverContainerState.of(popoverContext).closeAll(); }, selectedFormat: state.typeOption.format, ), diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/select_option.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/select_option.dart index d36df1fea7..489b6ac8f5 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/select_option.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/select_option.dart @@ -1,6 +1,8 @@ import 'package:app_flowy/plugins/grid/application/field/type_option/select_option_type_option_bloc.dart'; +import 'package:appflowy_popover/popover.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui_web.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; @@ -143,54 +145,70 @@ class _OptionList extends StatelessWidget { _OptionCell _makeOptionCell(BuildContext context, SelectOptionPB option) { return _OptionCell( option: option, - onSelected: (option) { - final pannel = SelectOptionTypeOptionEditor( - option: option, - onDeleted: () { - delegate.hideOverlay(context); - context - .read() - .add(SelectOptionTypeOptionEvent.deleteOption(option)); - }, - onUpdated: (updatedOption) { - delegate.hideOverlay(context); - context - .read() - .add(SelectOptionTypeOptionEvent.updateOption(updatedOption)); - }, - key: ValueKey(option.id), - ); - delegate.showOverlay(context, pannel); - }, ); } } -class _OptionCell extends StatelessWidget { +class _OptionCell extends StatefulWidget { final SelectOptionPB option; - final Function(SelectOptionPB) onSelected; - const _OptionCell({ - required this.option, - required this.onSelected, - Key? key, - }) : super(key: key); + const _OptionCell({required this.option, Key? key}) : super(key: key); + + @override + State<_OptionCell> createState() => _OptionCellState(); +} + +class _OptionCellState extends State<_OptionCell> { + late PopoverController _popoverController; + + @override + void initState() { + _popoverController = PopoverController(); + super.initState(); + } @override Widget build(BuildContext context) { final theme = context.watch(); - return SizedBox( - height: GridSize.typeOptionItemHeight, - child: SelectOptionTagCell( - option: option, - onSelected: onSelected, - children: [ - svgWidget( - "grid/details", - color: theme.iconColor, - ), - ], + return Popover( + controller: _popoverController, + offset: const Offset(20, 0), + child: SizedBox( + height: GridSize.typeOptionItemHeight, + child: SelectOptionTagCell( + option: widget.option, + onSelected: (SelectOptionPB pb) { + _popoverController.show(); + }, + children: [ + svgWidget( + "grid/details", + color: theme.iconColor, + ), + ], + ), ), + popupBuilder: (BuildContext popoverContext) { + return OverlayContainer( + constraints: BoxConstraints.loose(const Size(460, 440)), + child: SelectOptionTypeOptionEditor( + option: widget.option, + onDeleted: () { + context + .read() + .add(SelectOptionTypeOptionEvent.deleteOption(widget.option)); + PopoverContainerState.of(popoverContext).closeAll(); + }, + onUpdated: (updatedOption) { + context + .read() + .add(SelectOptionTypeOptionEvent.updateOption(updatedOption)); + PopoverContainerState.of(popoverContext).closeAll(); + }, + key: ValueKey(widget.option.id), + ), + ); + }, ); } } diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/single_select.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/single_select.dart index d9d699fdff..3e889acd99 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/single_select.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/single_select.dart @@ -2,6 +2,7 @@ import 'package:app_flowy/plugins/grid/application/field/type_option/single_sele import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart'; import 'package:flutter/material.dart'; import '../field_type_option_editor.dart'; +import 'package:appflowy_popover/popover.dart'; import 'builder.dart'; import 'select_option.dart'; @@ -38,7 +39,10 @@ class SingleSelectTypeOptionWidget extends TypeOptionWidget { Widget build(BuildContext context) { return SelectOptionTypeOptionWidget( options: selectOptionAction.typeOption.options, - beginEdit: () => overlayDelegate.hideOverlay(context), + beginEdit: () { + overlayDelegate.hideOverlay(context); + PopoverContainerState.of(context).closeAll(); + }, overlayDelegate: overlayDelegate, typeOptionAction: selectOptionAction, // key: ValueKey(state.typeOption.hashCode), diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_property.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_property.dart index 1912dc8499..85eb4287ef 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_property.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_property.dart @@ -19,7 +19,7 @@ import 'package:styled_widget/styled_widget.dart'; import '../../../application/field/field_cache.dart'; import '../../layout/sizes.dart'; -class GridPropertyList extends StatelessWidget { +class GridPropertyList extends StatefulWidget { final String gridId; final GridFieldCache fieldCache; const GridPropertyList({ @@ -28,17 +28,34 @@ class GridPropertyList extends StatelessWidget { Key? key, }) : super(key: key); + @override + State createState() => _GridPropertyListState(); +} + +class _GridPropertyListState extends State { + late PopoverMutex _popoverMutex; + + @override + void initState() { + _popoverMutex = PopoverMutex(); + super.initState(); + } + @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => - getIt(param1: gridId, param2: fieldCache) - ..add(const GridPropertyEvent.initial()), + create: (context) => getIt( + param1: widget.gridId, param2: widget.fieldCache) + ..add(const GridPropertyEvent.initial()), child: BlocBuilder( builder: (context, state) { final cells = state.fields.map((field) { return _GridPropertyCell( - gridId: gridId, field: field, key: ValueKey(field.id)); + popoverMutex: _popoverMutex, + gridId: widget.gridId, + field: field, + key: ValueKey(field.id), + ); }).toList(); return ListView.separated( @@ -60,8 +77,13 @@ class GridPropertyList extends StatelessWidget { class _GridPropertyCell extends StatelessWidget { final FieldPB field; final String gridId; - const _GridPropertyCell({required this.gridId, required this.field, Key? key}) - : super(key: key); + final PopoverMutex popoverMutex; + const _GridPropertyCell({ + required this.gridId, + required this.field, + required this.popoverMutex, + Key? key, + }) : super(key: key); @override Widget build(BuildContext context) { @@ -95,6 +117,7 @@ class _GridPropertyCell extends StatelessWidget { Widget _editFieldButton(AppTheme theme, BuildContext context) { return Popover( + mutex: popoverMutex, triggerActions: PopoverTriggerActionFlags.click, offset: const Offset(20, 0), child: FlowyButton( diff --git a/frontend/app_flowy/packages/appflowy_popover/lib/popover.dart b/frontend/app_flowy/packages/appflowy_popover/lib/popover.dart index df51f7249d..a22ac781f9 100644 --- a/frontend/app_flowy/packages/appflowy_popover/lib/popover.dart +++ b/frontend/app_flowy/packages/appflowy_popover/lib/popover.dart @@ -1,10 +1,7 @@ -import 'dart:ui'; - import 'package:appflowy_popover/layout.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import './follower.dart'; class PopoverMutex { PopoverState? state; @@ -128,6 +125,7 @@ class PopoverState extends State { offset: widget.offset ?? Offset.zero, popupBuilder: widget.popupBuilder, onClose: () => close(), + onCloseAll: () => closeAll(), )); return Stack(children: children); @@ -155,6 +153,10 @@ class PopoverState extends State { } } + closeAll() { + _popoverWithMask?.close(); + } + @override void deactivate() { debugPrint("deactivate"); @@ -247,6 +249,7 @@ class PopoverContainer extends StatefulWidget { final PopoverLink popoverLink; final Offset offset; final void Function() onClose; + final void Function() onCloseAll; const PopoverContainer({ Key? key, @@ -255,6 +258,7 @@ class PopoverContainer extends StatefulWidget { required this.popoverLink, required this.offset, required this.onClose, + required this.onCloseAll, }) : super(key: key); @override @@ -274,9 +278,9 @@ class PopoverContainerState extends State { ); } - close() { - widget.onClose(); - } + close() => widget.onClose(); + + closeAll() => widget.onCloseAll(); static PopoverContainerState of(BuildContext context) { if (context is StatefulElement && context.state is PopoverContainerState) {