feat: add mutex to property list

This commit is contained in:
Vincent Chan 2022-08-31 19:15:20 +08:00
parent 4c31c0dcf0
commit 8229371f63
11 changed files with 198 additions and 128 deletions

View File

@ -1,6 +1,7 @@
import 'dart:collection'; import 'dart:collection';
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart'; 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: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/image.dart';
import 'package:flowy_infra/theme.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 SelectOptionPB option;
final bool isSelected; final bool isSelected;
const _SelectOptionCell(this.option, this.isSelected, {Key? key}) const _SelectOptionCell(this.option, this.isSelected, {Key? key})
: super(key: key); : super(key: key);
@override @override
Widget build(BuildContext context) { State<_SelectOptionCell> createState() => _SelectOptionCellState();
final theme = context.watch<AppTheme>(); }
return SizedBox(
height: GridSize.typeOptionItemHeight, class _SelectOptionCellState extends State<_SelectOptionCell> {
child: Row( late PopoverController _popoverController;
children: [
Flexible( @override
fit: FlexFit.loose, void initState() {
child: SelectOptionTagCell( _popoverController = PopoverController();
option: option, super.initState();
onSelected: (option) {
context
.read<SelectOptionCellEditorBloc>()
.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),
)
],
),
);
} }
void _showEditPannel(BuildContext context) { @override
final pannel = SelectOptionTypeOptionEditor( Widget build(BuildContext context) {
option: option, final theme = context.watch<AppTheme>();
onDeleted: () { return Popover(
context controller: _popoverController,
.read<SelectOptionCellEditorBloc>() offset: const Offset(20, 0),
.add(SelectOptionEditorEvent.deleteOption(option)); child: SizedBox(
}, height: GridSize.typeOptionItemHeight,
onUpdated: (updatedOption) { child: Row(
context children: [
.read<SelectOptionCellEditorBloc>() Flexible(
.add(SelectOptionEditorEvent.updateOption(updatedOption)); fit: FlexFit.loose,
}, child: SelectOptionTagCell(
key: ValueKey(option option: widget.option,
.id), // Use ValueKey to refresh the UI, otherwise, it will remain the old value. onSelected: (option) {
); context
final overlayIdentifier = (SelectOptionTypeOptionEditor).toString(); .read<SelectOptionCellEditorBloc>()
.add(SelectOptionEditorEvent.selectOption(option.id));
FlowyOverlay.of(context).remove(overlayIdentifier); },
FlowyOverlay.of(context).insertWithAnchor( children: [
widget: OverlayContainer( if (widget.isSelected)
child: pannel, Padding(
constraints: BoxConstraints.loose(const Size(200, 300)), 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, popupBuilder: (BuildContext popoverContext) {
anchorContext: context, return OverlayContainer(
anchorDirection: AnchorDirection.rightWithCenterAligned, constraints: BoxConstraints.loose(const Size(200, 300)),
anchorOffset: Offset(2 * overlayContainerPadding.left, 0), child: SelectOptionTypeOptionEditor(
option: widget.option,
onDeleted: () {
context
.read<SelectOptionCellEditorBloc>()
.add(SelectOptionEditorEvent.deleteOption(widget.option));
},
onUpdated: (updatedOption) {
context
.read<SelectOptionCellEditorBloc>()
.add(SelectOptionEditorEvent.updateOption(updatedOption));
},
key: ValueKey(widget.option
.id), // Use ValueKey to refresh the UI, otherwise, it will remain the old value.
),
);
},
); );
} }
} }

View File

@ -35,6 +35,7 @@ class _GridFieldCellState extends State<GridFieldCell> {
builder: (context, state) { builder: (context, state) {
final button = Popover( final button = Popover(
controller: popover, controller: popover,
direction: PopoverDirection.bottomWithLeftAligned,
child: FieldCellButton( child: FieldCellButton(
field: state.field, field: state.field,
onTap: () => popover.show(), onTap: () => popover.show(),

View File

@ -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:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:appflowy_popover/popover.dart'; import 'package:appflowy_popover/popover.dart';
import 'package:easy_localization/easy_localization.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/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';

View File

@ -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/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/plugins/grid/application/prelude.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/image.dart';
import 'package:flowy_infra/theme.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/button.dart';
import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
@ -154,19 +156,25 @@ class CreateFieldButton extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = context.watch<AppTheme>(); final theme = context.watch<AppTheme>();
return FlowyButton( return Popover(
text: const FlowyText.medium('New column', fontSize: 12), triggerActions: PopoverTriggerActionFlags.click,
hoverColor: theme.shader6, direction: PopoverDirection.bottomWithRightAligned,
onTap: () { child: FlowyButton(
// FieldEditorPopOver.show( text: const FlowyText.medium('New column', fontSize: 12),
// context, hoverColor: theme.shader6,
// anchorContext: context, onTap: () {},
// gridId: gridId, leftIcon: svgWidget("home/add"),
// fieldName: "", ),
// typeOptionLoader: NewFieldTypeOptionLoader(gridId: gridId), 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"),
); );
} }
} }

View File

@ -77,6 +77,7 @@ class DateTypeOptionWidget extends TypeOptionWidget {
context context
.read<DateTypeOptionBloc>() .read<DateTypeOptionBloc>()
.add(DateTypeOptionEvent.didSelectDateFormat(format)); .add(DateTypeOptionEvent.didSelectDateFormat(format));
PopoverContainerState.of(popoverContext).closeAll();
}, },
), ),
); );
@ -100,7 +101,7 @@ class DateTypeOptionWidget extends TypeOptionWidget {
context context
.read<DateTypeOptionBloc>() .read<DateTypeOptionBloc>()
.add(DateTypeOptionEvent.didSelectTimeFormat(format)); .add(DateTypeOptionEvent.didSelectTimeFormat(format));
PopoverContainerState.of(popoverContext).close(); PopoverContainerState.of(popoverContext).closeAll();
}), }),
); );
}, },

View File

@ -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/multi_select_type_option.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart'; import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:appflowy_popover/popover.dart';
import '../field_type_option_editor.dart'; import '../field_type_option_editor.dart';
import 'builder.dart'; import 'builder.dart';
@ -39,7 +40,10 @@ class MultiSelectTypeOptionWidget extends TypeOptionWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SelectOptionTypeOptionWidget( return SelectOptionTypeOptionWidget(
options: selectOptionAction.typeOption.options, options: selectOptionAction.typeOption.options,
beginEdit: () => overlayDelegate.hideOverlay(context), beginEdit: () {
overlayDelegate.hideOverlay(context);
PopoverContainerState.of(context).closeAll();
},
overlayDelegate: overlayDelegate, overlayDelegate: overlayDelegate,
typeOptionAction: selectOptionAction, typeOptionAction: selectOptionAction,
// key: ValueKey(state.typeOption.hashCode), // key: ValueKey(state.typeOption.hashCode),

View File

@ -79,7 +79,7 @@ class NumberTypeOptionWidget extends TypeOptionWidget {
], ],
), ),
), ),
popupBuilder: (BuildContext context) { popupBuilder: (BuildContext popoverContext) {
return OverlayContainer( return OverlayContainer(
constraints: BoxConstraints.loose(const Size(460, 440)), constraints: BoxConstraints.loose(const Size(460, 440)),
child: NumberFormatList( child: NumberFormatList(
@ -87,6 +87,7 @@ class NumberTypeOptionWidget extends TypeOptionWidget {
context context
.read<NumberTypeOptionBloc>() .read<NumberTypeOptionBloc>()
.add(NumberTypeOptionEvent.didSelectFormat(format)); .add(NumberTypeOptionEvent.didSelectFormat(format));
PopoverContainerState.of(popoverContext).closeAll();
}, },
selectedFormat: state.typeOption.format, selectedFormat: state.typeOption.format,
), ),

View File

@ -1,6 +1,8 @@
import 'package:app_flowy/plugins/grid/application/field/type_option/select_option_type_option_bloc.dart'; 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/image.dart';
import 'package:flowy_infra/theme.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/button.dart';
import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flowy_infra_ui/widget/spacing.dart';
@ -143,54 +145,70 @@ class _OptionList extends StatelessWidget {
_OptionCell _makeOptionCell(BuildContext context, SelectOptionPB option) { _OptionCell _makeOptionCell(BuildContext context, SelectOptionPB option) {
return _OptionCell( return _OptionCell(
option: option, option: option,
onSelected: (option) {
final pannel = SelectOptionTypeOptionEditor(
option: option,
onDeleted: () {
delegate.hideOverlay(context);
context
.read<SelectOptionTypeOptionBloc>()
.add(SelectOptionTypeOptionEvent.deleteOption(option));
},
onUpdated: (updatedOption) {
delegate.hideOverlay(context);
context
.read<SelectOptionTypeOptionBloc>()
.add(SelectOptionTypeOptionEvent.updateOption(updatedOption));
},
key: ValueKey(option.id),
);
delegate.showOverlay(context, pannel);
},
); );
} }
} }
class _OptionCell extends StatelessWidget { class _OptionCell extends StatefulWidget {
final SelectOptionPB option; final SelectOptionPB option;
final Function(SelectOptionPB) onSelected; const _OptionCell({required this.option, Key? key}) : super(key: key);
const _OptionCell({
required this.option, @override
required this.onSelected, State<_OptionCell> createState() => _OptionCellState();
Key? key, }
}) : super(key: key);
class _OptionCellState extends State<_OptionCell> {
late PopoverController _popoverController;
@override
void initState() {
_popoverController = PopoverController();
super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = context.watch<AppTheme>(); final theme = context.watch<AppTheme>();
return SizedBox( return Popover(
height: GridSize.typeOptionItemHeight, controller: _popoverController,
child: SelectOptionTagCell( offset: const Offset(20, 0),
option: option, child: SizedBox(
onSelected: onSelected, height: GridSize.typeOptionItemHeight,
children: [ child: SelectOptionTagCell(
svgWidget( option: widget.option,
"grid/details", onSelected: (SelectOptionPB pb) {
color: theme.iconColor, _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<SelectOptionTypeOptionBloc>()
.add(SelectOptionTypeOptionEvent.deleteOption(widget.option));
PopoverContainerState.of(popoverContext).closeAll();
},
onUpdated: (updatedOption) {
context
.read<SelectOptionTypeOptionBloc>()
.add(SelectOptionTypeOptionEvent.updateOption(updatedOption));
PopoverContainerState.of(popoverContext).closeAll();
},
key: ValueKey(widget.option.id),
),
);
},
); );
} }
} }

View File

@ -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:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../field_type_option_editor.dart'; import '../field_type_option_editor.dart';
import 'package:appflowy_popover/popover.dart';
import 'builder.dart'; import 'builder.dart';
import 'select_option.dart'; import 'select_option.dart';
@ -38,7 +39,10 @@ class SingleSelectTypeOptionWidget extends TypeOptionWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SelectOptionTypeOptionWidget( return SelectOptionTypeOptionWidget(
options: selectOptionAction.typeOption.options, options: selectOptionAction.typeOption.options,
beginEdit: () => overlayDelegate.hideOverlay(context), beginEdit: () {
overlayDelegate.hideOverlay(context);
PopoverContainerState.of(context).closeAll();
},
overlayDelegate: overlayDelegate, overlayDelegate: overlayDelegate,
typeOptionAction: selectOptionAction, typeOptionAction: selectOptionAction,
// key: ValueKey(state.typeOption.hashCode), // key: ValueKey(state.typeOption.hashCode),

View File

@ -19,7 +19,7 @@ import 'package:styled_widget/styled_widget.dart';
import '../../../application/field/field_cache.dart'; import '../../../application/field/field_cache.dart';
import '../../layout/sizes.dart'; import '../../layout/sizes.dart';
class GridPropertyList extends StatelessWidget { class GridPropertyList extends StatefulWidget {
final String gridId; final String gridId;
final GridFieldCache fieldCache; final GridFieldCache fieldCache;
const GridPropertyList({ const GridPropertyList({
@ -28,17 +28,34 @@ class GridPropertyList extends StatelessWidget {
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@override
State<StatefulWidget> createState() => _GridPropertyListState();
}
class _GridPropertyListState extends State<GridPropertyList> {
late PopoverMutex _popoverMutex;
@override
void initState() {
_popoverMutex = PopoverMutex();
super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => create: (context) => getIt<GridPropertyBloc>(
getIt<GridPropertyBloc>(param1: gridId, param2: fieldCache) param1: widget.gridId, param2: widget.fieldCache)
..add(const GridPropertyEvent.initial()), ..add(const GridPropertyEvent.initial()),
child: BlocBuilder<GridPropertyBloc, GridPropertyState>( child: BlocBuilder<GridPropertyBloc, GridPropertyState>(
builder: (context, state) { builder: (context, state) {
final cells = state.fields.map((field) { final cells = state.fields.map((field) {
return _GridPropertyCell( return _GridPropertyCell(
gridId: gridId, field: field, key: ValueKey(field.id)); popoverMutex: _popoverMutex,
gridId: widget.gridId,
field: field,
key: ValueKey(field.id),
);
}).toList(); }).toList();
return ListView.separated( return ListView.separated(
@ -60,8 +77,13 @@ class GridPropertyList extends StatelessWidget {
class _GridPropertyCell extends StatelessWidget { class _GridPropertyCell extends StatelessWidget {
final FieldPB field; final FieldPB field;
final String gridId; final String gridId;
const _GridPropertyCell({required this.gridId, required this.field, Key? key}) final PopoverMutex popoverMutex;
: super(key: key); const _GridPropertyCell({
required this.gridId,
required this.field,
required this.popoverMutex,
Key? key,
}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -95,6 +117,7 @@ class _GridPropertyCell extends StatelessWidget {
Widget _editFieldButton(AppTheme theme, BuildContext context) { Widget _editFieldButton(AppTheme theme, BuildContext context) {
return Popover( return Popover(
mutex: popoverMutex,
triggerActions: PopoverTriggerActionFlags.click, triggerActions: PopoverTriggerActionFlags.click,
offset: const Offset(20, 0), offset: const Offset(20, 0),
child: FlowyButton( child: FlowyButton(

View File

@ -1,10 +1,7 @@
import 'dart:ui';
import 'package:appflowy_popover/layout.dart'; import 'package:appflowy_popover/layout.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import './follower.dart';
class PopoverMutex { class PopoverMutex {
PopoverState? state; PopoverState? state;
@ -128,6 +125,7 @@ class PopoverState extends State<Popover> {
offset: widget.offset ?? Offset.zero, offset: widget.offset ?? Offset.zero,
popupBuilder: widget.popupBuilder, popupBuilder: widget.popupBuilder,
onClose: () => close(), onClose: () => close(),
onCloseAll: () => closeAll(),
)); ));
return Stack(children: children); return Stack(children: children);
@ -155,6 +153,10 @@ class PopoverState extends State<Popover> {
} }
} }
closeAll() {
_popoverWithMask?.close();
}
@override @override
void deactivate() { void deactivate() {
debugPrint("deactivate"); debugPrint("deactivate");
@ -247,6 +249,7 @@ class PopoverContainer extends StatefulWidget {
final PopoverLink popoverLink; final PopoverLink popoverLink;
final Offset offset; final Offset offset;
final void Function() onClose; final void Function() onClose;
final void Function() onCloseAll;
const PopoverContainer({ const PopoverContainer({
Key? key, Key? key,
@ -255,6 +258,7 @@ class PopoverContainer extends StatefulWidget {
required this.popoverLink, required this.popoverLink,
required this.offset, required this.offset,
required this.onClose, required this.onClose,
required this.onCloseAll,
}) : super(key: key); }) : super(key: key);
@override @override
@ -274,9 +278,9 @@ class PopoverContainerState extends State<PopoverContainer> {
); );
} }
close() { close() => widget.onClose();
widget.onClose();
} closeAll() => widget.onCloseAll();
static PopoverContainerState of(BuildContext context) { static PopoverContainerState of(BuildContext context) {
if (context is StatefulElement && context.state is PopoverContainerState) { if (context is StatefulElement && context.state is PopoverContainerState) {