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 '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<AppTheme>();
return SizedBox(
height: GridSize.typeOptionItemHeight,
child: Row(
children: [
Flexible(
fit: FlexFit.loose,
child: SelectOptionTagCell(
option: option,
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),
)
],
),
);
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<SelectOptionCellEditorBloc>()
.add(SelectOptionEditorEvent.deleteOption(option));
},
onUpdated: (updatedOption) {
context
.read<SelectOptionCellEditorBloc>()
.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<AppTheme>();
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<SelectOptionCellEditorBloc>()
.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<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) {
final button = Popover(
controller: popover,
direction: PopoverDirection.bottomWithLeftAligned,
child: FieldCellButton(
field: state.field,
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: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';

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/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<AppTheme>();
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"),
);
}
}

View File

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

View File

@ -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<NumberTypeOptionBloc>()
.add(NumberTypeOptionEvent.didSelectFormat(format));
PopoverContainerState.of(popoverContext).closeAll();
},
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: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<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 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<AppTheme>();
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<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: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),

View File

@ -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<StatefulWidget> createState() => _GridPropertyListState();
}
class _GridPropertyListState extends State<GridPropertyList> {
late PopoverMutex _popoverMutex;
@override
void initState() {
_popoverMutex = PopoverMutex();
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) =>
getIt<GridPropertyBloc>(param1: gridId, param2: fieldCache)
..add(const GridPropertyEvent.initial()),
create: (context) => getIt<GridPropertyBloc>(
param1: widget.gridId, param2: widget.fieldCache)
..add(const GridPropertyEvent.initial()),
child: BlocBuilder<GridPropertyBloc, GridPropertyState>(
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(

View File

@ -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<Popover> {
offset: widget.offset ?? Offset.zero,
popupBuilder: widget.popupBuilder,
onClose: () => close(),
onCloseAll: () => closeAll(),
));
return Stack(children: children);
@ -155,6 +153,10 @@ class PopoverState extends State<Popover> {
}
}
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<PopoverContainer> {
);
}
close() {
widget.onClose();
}
close() => widget.onClose();
closeAll() => widget.onCloseAll();
static PopoverContainerState of(BuildContext context) {
if (context is StatefulElement && context.state is PopoverContainerState) {