feat: refactor multi select

This commit is contained in:
Vincent Chan 2022-08-30 14:02:06 +08:00
parent e9535201a4
commit bfb60dcaec
9 changed files with 188 additions and 81 deletions

View File

@ -1,5 +1,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';
@ -10,7 +11,7 @@ import 'package:app_flowy/generated/locale_keys.g.dart';
import 'field_name_input.dart';
import 'field_type_option_editor.dart';
class FieldEditor extends StatelessWidget {
class FieldEditor extends StatefulWidget {
final String gridId;
final String fieldName;
@ -22,13 +23,29 @@ class FieldEditor extends StatelessWidget {
Key? key,
}) : super(key: key);
@override
bool asBarrier() => true;
@override
State<StatefulWidget> createState() => _FieldEditorState();
}
class _FieldEditorState extends State<FieldEditor> {
late PopoverMutex popoverMutex;
@override
void initState() {
popoverMutex = PopoverMutex();
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => FieldEditorBloc(
gridId: gridId,
fieldName: fieldName,
loader: typeOptionLoader,
gridId: widget.gridId,
fieldName: widget.fieldName,
loader: widget.typeOptionLoader,
)..add(const FieldEditorEvent.initial()),
child: BlocBuilder<FieldEditorBloc, FieldEditorState>(
buildWhen: (p, c) => false,
@ -41,20 +58,22 @@ class FieldEditor extends StatelessWidget {
const VSpace(10),
const _FieldNameCell(),
const VSpace(10),
const _FieldTypeOptionCell(),
_FieldTypeOptionCell(popoverMutex: popoverMutex),
],
);
},
),
);
}
@override
bool asBarrier() => true;
}
class _FieldTypeOptionCell extends StatelessWidget {
const _FieldTypeOptionCell({Key? key}) : super(key: key);
final PopoverMutex popoverMutex;
const _FieldTypeOptionCell({
Key? key,
required this.popoverMutex,
}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -66,7 +85,10 @@ class _FieldTypeOptionCell extends StatelessWidget {
(fieldContext) {
final dataController =
context.read<FieldEditorBloc>().dataController;
return FieldTypeOptionEditor(dataController: dataController);
return FieldTypeOptionEditor(
dataController: dataController,
popoverMutex: popoverMutex,
);
},
);
},

View File

@ -26,9 +26,11 @@ typedef SwitchToFieldCallback
class FieldTypeOptionEditor extends StatefulWidget {
final TypeOptionDataController dataController;
final PopoverMutex popoverMutex;
const FieldTypeOptionEditor({
required this.dataController,
required this.popoverMutex,
Key? key,
}) : super(key: key);
@ -37,9 +39,15 @@ class FieldTypeOptionEditor extends StatefulWidget {
}
class _FieldTypeOptionEditorState extends State<FieldTypeOptionEditor> {
final popover = PopoverController();
late PopoverController popover;
String? currentOverlayIdentifier;
@override
void initState() {
popover = PopoverController(mutex: widget.popoverMutex);
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocProvider(
@ -114,6 +122,7 @@ class _FieldTypeOptionEditorState extends State<FieldTypeOptionEditor> {
context: context,
overlayDelegate: overlayDelegate,
dataController: widget.dataController,
popoverMutex: widget.popoverMutex,
);
}
@ -138,6 +147,6 @@ class _FieldTypeOptionEditorState extends State<FieldTypeOptionEditor> {
}
}
abstract class TypeOptionWidget extends StatelessWidget {
abstract class TypeOptionWidget extends StatefulWidget {
const TypeOptionWidget({Key? key}) : super(key: key);
}

View File

@ -2,6 +2,7 @@ import 'dart:typed_data';
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_data_controller.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_type_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/multi_select_type_option.pb.dart';
@ -46,18 +47,19 @@ Widget? makeTypeOptionWidget({
required BuildContext context,
required TypeOptionDataController dataController,
required TypeOptionOverlayDelegate overlayDelegate,
required PopoverMutex popoverMutex,
}) {
final builder = makeTypeOptionWidgetBuilder(
dataController: dataController,
overlayDelegate: overlayDelegate,
);
dataController: dataController,
overlayDelegate: overlayDelegate,
popoverMutex: popoverMutex);
return builder.build(context);
}
TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder({
required TypeOptionDataController dataController,
required TypeOptionOverlayDelegate overlayDelegate,
}) {
TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder(
{required TypeOptionDataController dataController,
required TypeOptionOverlayDelegate overlayDelegate,
required PopoverMutex popoverMutex}) {
final gridId = dataController.gridId;
final fieldType = dataController.field.fieldType;
@ -72,13 +74,12 @@ TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder({
);
case FieldType.DateTime:
return DateTypeOptionWidgetBuilder(
makeTypeOptionContextWithDataController<DateTypeOptionPB>(
gridId: gridId,
fieldType: fieldType,
dataController: dataController,
),
overlayDelegate,
);
makeTypeOptionContextWithDataController<DateTypeOptionPB>(
gridId: gridId,
fieldType: fieldType,
dataController: dataController,
),
popoverMutex);
case FieldType.SingleSelect:
return SingleSelectTypeOptionWidgetBuilder(
makeTypeOptionContextWithDataController<SingleSelectTypeOptionPB>(

View File

@ -11,6 +11,7 @@ import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option_entities.pb.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:appflowy_popover/popover.dart';
import '../../../layout/sizes.dart';
import '../field_type_option_editor.dart';
import 'builder.dart';
@ -20,10 +21,10 @@ class DateTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
DateTypeOptionWidgetBuilder(
DateTypeOptionContext typeOptionContext,
TypeOptionOverlayDelegate overlayDelegate,
PopoverMutex popoverMutex,
) : _widget = DateTypeOptionWidget(
typeOptionContext: typeOptionContext,
overlayDelegate: overlayDelegate,
popoverMutex: popoverMutex,
);
@override
@ -34,22 +35,36 @@ class DateTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
class DateTypeOptionWidget extends TypeOptionWidget {
final DateTypeOptionContext typeOptionContext;
final TypeOptionOverlayDelegate overlayDelegate;
final PopoverMutex popoverMutex;
const DateTypeOptionWidget({
required this.typeOptionContext,
required this.overlayDelegate,
required this.popoverMutex,
Key? key,
}) : super(key: key);
@override
State<StatefulWidget> createState() => _DateTypeOptionWidgetState();
}
class _DateTypeOptionWidgetState extends State<DateTypeOptionWidget> {
late PopoverController dateFormatPopover;
late PopoverController timeFormatPopover;
@override
void initState() {
dateFormatPopover = PopoverController(mutex: widget.popoverMutex);
timeFormatPopover = PopoverController(mutex: widget.popoverMutex);
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) =>
DateTypeOptionBloc(typeOptionContext: typeOptionContext),
DateTypeOptionBloc(typeOptionContext: widget.typeOptionContext),
child: BlocConsumer<DateTypeOptionBloc, DateTypeOptionState>(
listener: (context, state) =>
typeOptionContext.typeOption = state.typeOption,
widget.typeOptionContext.typeOption = state.typeOption,
builder: (context, state) {
return Column(children: [
_renderDateFormatButton(context, state.typeOption.dateFormat),
@ -62,39 +77,71 @@ class DateTypeOptionWidget extends TypeOptionWidget {
}
Widget _renderDateFormatButton(BuildContext context, DateFormat dataFormat) {
return DateFormatButton(onTap: () {
final list = DateFormatList(
selectedFormat: dataFormat,
onSelected: (format) {
context
.read<DateTypeOptionBloc>()
.add(DateTypeOptionEvent.didSelectDateFormat(format));
},
);
overlayDelegate.showOverlay(context, list);
});
}
Widget _renderTimeFormatButton(BuildContext context, TimeFormat timeFormat) {
return TimeFormatButton(
timeFormat: timeFormat,
onTap: () {
final list = TimeFormatList(
selectedFormat: timeFormat,
return Popover(
controller: dateFormatPopover,
child: DateFormatButton(onTap: () {
dateFormatPopover.show();
}, onHover: ((hover) {
if (hover) {
dateFormatPopover.show();
}
})),
targetAnchor: Alignment.topRight,
followerAnchor: Alignment.topLeft,
offset: const Offset(20, 0),
popupBuilder: (context) {
return OverlayContainer(
constraints: BoxConstraints.loose(const Size(460, 440)),
child: DateFormatList(
selectedFormat: dataFormat,
onSelected: (format) {
context
.read<DateTypeOptionBloc>()
.add(DateTypeOptionEvent.didSelectTimeFormat(format));
});
overlayDelegate.showOverlay(context, list);
.add(DateTypeOptionEvent.didSelectDateFormat(format));
},
),
);
},
);
}
Widget _renderTimeFormatButton(BuildContext context, TimeFormat timeFormat) {
return Popover(
controller: timeFormatPopover,
child: TimeFormatButton(
timeFormat: timeFormat,
onTap: () {
timeFormatPopover.show();
},
onHover: (hover) {
if (hover) {
timeFormatPopover.show();
}
},
),
targetAnchor: Alignment.topRight,
followerAnchor: Alignment.topLeft,
offset: const Offset(20, 0),
popupBuilder: (BuildContext context) {
return OverlayContainer(
constraints: BoxConstraints.loose(const Size(460, 440)),
child: TimeFormatList(
selectedFormat: timeFormat,
onSelected: (format) {
context
.read<DateTypeOptionBloc>()
.add(DateTypeOptionEvent.didSelectTimeFormat(format));
}));
},
);
}
}
class DateFormatButton extends StatelessWidget {
final VoidCallback onTap;
const DateFormatButton({required this.onTap, Key? key}) : super(key: key);
final VoidCallback? onTap;
final void Function(bool)? onHover;
const DateFormatButton({this.onTap, this.onHover, Key? key})
: super(key: key);
@override
Widget build(BuildContext context) {
@ -107,6 +154,7 @@ class DateFormatButton extends StatelessWidget {
margin: GridSize.typeOptionContentInsets,
hoverColor: theme.hover,
onTap: onTap,
onHover: onHover,
rightIcon: svgWidget("grid/more", color: theme.iconColor),
),
);
@ -115,9 +163,10 @@ class DateFormatButton extends StatelessWidget {
class TimeFormatButton extends StatelessWidget {
final TimeFormat timeFormat;
final VoidCallback onTap;
final VoidCallback? onTap;
final void Function(bool)? onHover;
const TimeFormatButton(
{required this.timeFormat, required this.onTap, Key? key})
{required this.timeFormat, this.onTap, this.onHover, Key? key})
: super(key: key);
@override
@ -131,6 +180,7 @@ class TimeFormatButton extends StatelessWidget {
margin: GridSize.typeOptionContentInsets,
hoverColor: theme.hover,
onTap: onTap,
onHover: onHover,
rightIcon: svgWidget("grid/more", color: theme.iconColor),
),
);

View File

@ -35,13 +35,19 @@ class MultiSelectTypeOptionWidget extends TypeOptionWidget {
Key? key,
}) : super(key: key);
@override
State<StatefulWidget> createState() => _MultiSelectTypeOptionWidgetState();
}
class _MultiSelectTypeOptionWidgetState
extends State<MultiSelectTypeOptionWidget> {
@override
Widget build(BuildContext context) {
return SelectOptionTypeOptionWidget(
options: selectOptionAction.typeOption.options,
beginEdit: () => overlayDelegate.hideOverlay(context),
overlayDelegate: overlayDelegate,
typeOptionAction: selectOptionAction,
options: widget.selectOptionAction.typeOption.options,
beginEdit: () => widget.overlayDelegate.hideOverlay(context),
overlayDelegate: widget.overlayDelegate,
typeOptionAction: widget.selectOptionAction,
// key: ValueKey(state.typeOption.hashCode),
);
}

View File

@ -42,17 +42,22 @@ class NumberTypeOptionWidget extends TypeOptionWidget {
Key? key,
}) : super(key: key);
@override
State<StatefulWidget> createState() => _NumberTypeOptionWidgetState();
}
class _NumberTypeOptionWidgetState extends State<NumberTypeOptionWidget> {
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return BlocProvider(
create: (context) =>
NumberTypeOptionBloc(typeOptionContext: typeOptionContext),
NumberTypeOptionBloc(typeOptionContext: widget.typeOptionContext),
child: SizedBox(
height: GridSize.typeOptionItemHeight,
child: BlocConsumer<NumberTypeOptionBloc, NumberTypeOptionState>(
listener: (context, state) =>
typeOptionContext.typeOption = state.typeOption,
widget.typeOptionContext.typeOption = state.typeOption,
builder: (context, state) {
return FlowyButton(
text: Row(
@ -76,7 +81,7 @@ class NumberTypeOptionWidget extends TypeOptionWidget {
},
selectedFormat: state.typeOption.format,
);
overlayDelegate.showOverlay(
widget.overlayDelegate.showOverlay(
context,
list,
);

View File

@ -34,13 +34,19 @@ class SingleSelectTypeOptionWidget extends TypeOptionWidget {
Key? key,
}) : super(key: key);
@override
State<StatefulWidget> createState() => _SingleSelectTypeOptionWidgetState();
}
class _SingleSelectTypeOptionWidgetState
extends State<SingleSelectTypeOptionWidget> {
@override
Widget build(BuildContext context) {
return SelectOptionTypeOptionWidget(
options: selectOptionAction.typeOption.options,
beginEdit: () => overlayDelegate.hideOverlay(context),
overlayDelegate: overlayDelegate,
typeOptionAction: selectOptionAction,
options: widget.selectOptionAction.typeOption.options,
beginEdit: () => widget.overlayDelegate.hideOverlay(context),
overlayDelegate: widget.overlayDelegate,
typeOptionAction: widget.selectOptionAction,
// key: ValueKey(state.typeOption.hashCode),
);
}

View File

@ -7,14 +7,14 @@ class PopoverMenu extends StatefulWidget {
}
class _PopoverMenuState extends State<PopoverMenu> {
final PopoverExclusive exclusive = PopoverExclusive();
final PopoverMutex exclusive = PopoverMutex();
late PopoverController firstPopover;
late PopoverController secondPopover;
@override
void initState() {
firstPopover = PopoverController(exclusive: exclusive);
secondPopover = PopoverController(exclusive: exclusive);
firstPopover = PopoverController(mutex: exclusive);
secondPopover = PopoverController(mutex: exclusive);
super.initState();
}

View File

@ -2,28 +2,28 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class PopoverExclusive {
class PopoverMutex {
PopoverController? controller;
}
class PopoverController {
PopoverState? state;
PopoverExclusive? exclusive;
PopoverMutex? mutex;
PopoverController({this.exclusive});
PopoverController({this.mutex});
close() {
state?.close();
if (exclusive != null && exclusive!.controller == this) {
exclusive!.controller = null;
if (mutex != null && mutex!.controller == this) {
mutex!.controller = null;
}
}
show() {
if (exclusive != null) {
if (mutex != null) {
debugPrint("show popover");
exclusive!.controller?.close();
exclusive!.controller = this;
mutex!.controller?.close();
mutex!.controller = this;
}
state?.showOverlay();
}
@ -71,9 +71,15 @@ class PopoverState extends State<Popover> {
_recognizer.onTap = (() {
debugPrint("ggg tap");
});
WidgetsBinding.instance.pointerRouter
.addGlobalRoute(_handleGlobalPointerEvent);
super.initState();
}
_handleGlobalPointerEvent(PointerEvent event) {
// debugPrint("mouse down: ${event}");
}
showOverlay() {
debugPrint("show overlay");
close();
@ -126,6 +132,8 @@ class PopoverState extends State<Popover> {
@override
void deactivate() {
debugPrint("deactivate");
WidgetsBinding.instance.pointerRouter
.removeGlobalRoute(_handleGlobalPointerEvent);
close();
super.deactivate();
}