Merge pull request #1097 from AppFlowy-IO/refactor/appflowy_overlay

Refactor/appflowy overlay
This commit is contained in:
Nathan.fooo 2022-09-20 11:57:56 +08:00 committed by GitHub
commit 56e68d1346
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 483 additions and 418 deletions

View File

@ -2,7 +2,7 @@ import 'package:app_flowy/plugins/board/application/card/board_select_option_cel
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/extension.dart';
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

View File

@ -1,7 +1,7 @@
import 'package:app_flowy/plugins/board/application/card/card_bloc.dart';
import 'package:app_flowy/plugins/board/application/card/card_data_controller.dart';
import 'package:app_flowy/plugins/grid/presentation/widgets/row/row_action_sheet.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';

View File

@ -4,7 +4,7 @@ import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
import 'package:app_flowy/plugins/grid/presentation/layout/sizes.dart';
import 'package:app_flowy/plugins/grid/presentation/widgets/toolbar/grid_group.dart';
import 'package:app_flowy/plugins/grid/presentation/widgets/toolbar/grid_property.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';

View File

@ -1,5 +1,5 @@
import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -65,7 +65,7 @@ class _SettingButtonState extends State<_SettingButton> {
return AppFlowyPopover(
controller: popoverController,
constraints: BoxConstraints.loose(const Size(260, 400)),
triggerActions: PopoverTriggerActionFlags.click,
triggerActions: PopoverTriggerFlags.click,
child: FlowyIconButton(
hoverColor: theme.hover,
width: 22,

View File

@ -4,7 +4,7 @@ import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.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:appflowy_popover/appflowy_popover.dart';
import '../cell_builder.dart';
import 'date_editor.dart';

View File

@ -1,7 +1,7 @@
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:app_flowy/plugins/grid/application/cell/date_cal_bloc.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/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
@ -300,8 +300,7 @@ class _DateTypeOptionButton extends StatelessWidget {
selector: (state) => state.dateTypeOptionPB,
builder: (context, dateTypeOptionPB) {
return AppFlowyPopover(
triggerActions:
PopoverTriggerActionFlags.hover | PopoverTriggerActionFlags.click,
triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
offset: const Offset(20, 0),
constraints: BoxConstraints.loose(const Size(140, 100)),
child: FlowyButton(
@ -340,36 +339,30 @@ class _CalDateTimeSettingState extends State<_CalDateTimeSetting> {
@override
Widget build(BuildContext context) {
List<Widget> children = [
Popover(
AppFlowyPopover(
mutex: _popoverMutex,
triggerActions:
PopoverTriggerActionFlags.hover | PopoverTriggerActionFlags.click,
asBarrier: true,
triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
offset: const Offset(20, 0),
popupBuilder: (BuildContext context) {
return OverlayContainer(
constraints: BoxConstraints.loose(const Size(460, 440)),
child: DateFormatList(
selectedFormat: widget.dateTypeOptionPB.dateFormat,
onSelected: (format) =>
widget.onEvent(DateCalEvent.setDateFormat(format)),
),
return DateFormatList(
selectedFormat: widget.dateTypeOptionPB.dateFormat,
onSelected: (format) =>
widget.onEvent(DateCalEvent.setDateFormat(format)),
);
},
child: const DateFormatButton(),
),
Popover(
AppFlowyPopover(
mutex: _popoverMutex,
triggerActions:
PopoverTriggerActionFlags.hover | PopoverTriggerActionFlags.click,
asBarrier: true,
triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
offset: const Offset(20, 0),
popupBuilder: (BuildContext context) {
return OverlayContainer(
constraints: BoxConstraints.loose(const Size(460, 440)),
child: TimeFormatList(
selectedFormat: widget.dateTypeOptionPB.timeFormat,
onSelected: (format) =>
widget.onEvent(DateCalEvent.setTimeFormat(format)),
),
return TimeFormatList(
selectedFormat: widget.dateTypeOptionPB.timeFormat,
onSelected: (format) =>
widget.onEvent(DateCalEvent.setTimeFormat(format)),
);
},
child: TimeFormatButton(timeFormat: widget.dateTypeOptionPB.timeFormat),

View File

@ -1,6 +1,6 @@
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/plugins/grid/application/prelude.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';

View File

@ -1,7 +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:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
@ -243,6 +243,7 @@ class _SelectOptionCellState extends State<_SelectOptionCell> {
return AppFlowyPopover(
controller: _popoverController,
offset: const Offset(20, 0),
asBarrier: true,
constraints: BoxConstraints.loose(const Size(200, 300)),
mutex: widget.popoverMutex,
child: SizedBox(

View File

@ -2,7 +2,7 @@ import 'dart:async';
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:app_flowy/plugins/grid/application/cell/url_cell_bloc.dart';
import 'package:app_flowy/workspace/presentation/home/toast.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
@ -220,7 +220,7 @@ class _EditURLAccessoryState extends State<_EditURLAccessory>
constraints: BoxConstraints.loose(const Size(300, 160)),
controller: _popoverController,
direction: PopoverDirection.bottomWithLeftAligned,
triggerActions: PopoverTriggerActionFlags.click,
triggerActions: PopoverTriggerFlags.click,
offset: const Offset(0, 20),
child: svgWidget("editor/edit", color: theme.iconColor),
popupBuilder: (BuildContext popoverContext) {

View File

@ -1,6 +1,6 @@
import 'package:app_flowy/plugins/grid/application/field/field_cell_bloc.dart';
import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -33,7 +33,7 @@ class GridFieldCell extends StatelessWidget {
final button = AppFlowyPopover(
constraints: BoxConstraints.loose(const Size(240, 840)),
direction: PopoverDirection.bottomWithLeftAligned,
triggerActions: PopoverTriggerActionFlags.click,
triggerActions: PopoverTriggerFlags.click,
offset: const Offset(0, 10),
popupBuilder: (BuildContext context) {
return GridFieldCellActionSheet(

View File

@ -3,7 +3,7 @@ import 'package:app_flowy/plugins/grid/presentation/widgets/header/field_editor.
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/plugins/grid/application/prelude.dart';
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';

View File

@ -1,6 +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:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
import 'package:flowy_infra/theme.dart';
@ -244,7 +244,7 @@ class _DeleteFieldButton extends StatelessWidget {
Widget _wrapPopover(Widget widget) {
return AppFlowyPopover(
triggerActions: PopoverTriggerActionFlags.click,
triggerActions: PopoverTriggerFlags.click,
constraints: BoxConstraints.loose(const Size(400, 240)),
mutex: popoverMutex,
direction: PopoverDirection.center,

View File

@ -1,4 +1,4 @@
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';

View File

@ -1,6 +1,6 @@
import 'dart:typed_data';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_data_controller.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:dartz/dartz.dart' show Either;
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
@ -66,8 +66,8 @@ class FieldTypeOptionEditor extends StatelessWidget {
height: GridSize.typeOptionItemHeight,
child: AppFlowyPopover(
constraints: BoxConstraints.loose(const Size(460, 440)),
triggerActions:
PopoverTriggerActionFlags.click | PopoverTriggerActionFlags.hover,
asBarrier: true,
triggerActions: PopoverTriggerFlags.click | PopoverTriggerFlags.hover,
mutex: popoverMutex,
offset: const Offset(20, 0),
popupBuilder: (context) {

View File

@ -4,7 +4,7 @@ import 'package:app_flowy/plugins/grid/application/field/type_option/type_option
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/plugins/grid/application/prelude.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -177,9 +177,10 @@ class CreateFieldButton extends StatelessWidget {
final theme = context.watch<AppTheme>();
return AppFlowyPopover(
triggerActions: PopoverTriggerActionFlags.click,
triggerActions: PopoverTriggerFlags.click,
direction: PopoverDirection.bottomWithRightAligned,
constraints: BoxConstraints.loose(const Size(240, 200)),
asBarrier: true,
constraints: BoxConstraints.loose(const Size(240, 600)),
child: FlowyButton(
text: FlowyText.medium(
LocaleKeys.grid_field_newColumn.tr(),

View File

@ -3,7 +3,7 @@ import 'dart:typed_data';
import 'package:app_flowy/plugins/grid/application/field/field_controller.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_data_controller.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_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';

View File

@ -11,7 +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 'package:appflowy_popover/appflowy_popover.dart';
import '../../../layout/sizes.dart';
import '../field_type_option_editor.dart';
import 'builder.dart';
@ -64,8 +64,7 @@ class DateTypeOptionWidget extends TypeOptionWidget {
Widget _renderDateFormatButton(BuildContext context, DateFormat dataFormat) {
return AppFlowyPopover(
mutex: popoverMutex,
triggerActions:
PopoverTriggerActionFlags.hover | PopoverTriggerActionFlags.click,
triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
offset: const Offset(20, 0),
constraints: BoxConstraints.loose(const Size(460, 440)),
popupBuilder: (popoverContext) {
@ -86,8 +85,7 @@ class DateTypeOptionWidget extends TypeOptionWidget {
Widget _renderTimeFormatButton(BuildContext context, TimeFormat timeFormat) {
return AppFlowyPopover(
mutex: popoverMutex,
triggerActions:
PopoverTriggerActionFlags.hover | PopoverTriggerActionFlags.click,
triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
offset: const Offset(20, 0),
constraints: BoxConstraints.loose(const Size(460, 440)),
popupBuilder: (BuildContext popoverContext) {

View File

@ -1,7 +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 'package:appflowy_popover/appflowy_popover.dart';
import '../field_type_option_editor.dart';
import 'builder.dart';

View File

@ -1,7 +1,7 @@
import 'package:app_flowy/plugins/grid/application/field/type_option/number_bloc.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/number_format_bloc.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/appflowy_popover.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -57,8 +57,8 @@ class NumberTypeOptionWidget extends TypeOptionWidget {
builder: (context, state) {
return AppFlowyPopover(
mutex: popoverMutex,
triggerActions: PopoverTriggerActionFlags.hover |
PopoverTriggerActionFlags.click,
triggerActions:
PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
offset: const Offset(20, 0),
constraints: BoxConstraints.loose(const Size(460, 440)),
child: FlowyButton(

View File

@ -1,5 +1,5 @@
import 'package:app_flowy/plugins/grid/application/field/type_option/select_option_type_option_bloc.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -184,6 +184,7 @@ class _OptionCellState extends State<_OptionCell> {
controller: _popoverController,
mutex: widget.popoverMutex,
offset: const Offset(20, 0),
asBarrier: true,
constraints: BoxConstraints.loose(const Size(460, 440)),
child: SizedBox(
height: GridSize.typeOptionItemHeight,

View File

@ -2,7 +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 'package:appflowy_popover/appflowy_popover.dart';
import 'builder.dart';
import 'select_option.dart';

View File

@ -1,7 +1,7 @@
import 'package:app_flowy/plugins/grid/application/prelude.dart';
import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';

View File

@ -15,7 +15,7 @@ import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import '../../layout/sizes.dart';
import '../cell/cell_accessory.dart';
@ -197,7 +197,7 @@ class _CreateFieldButtonState extends State<_CreateFieldButton> {
return AppFlowyPopover(
constraints: BoxConstraints.loose(const Size(240, 200)),
controller: popoverController,
triggerActions: PopoverTriggerActionFlags.click,
triggerActions: PopoverTriggerFlags.click,
direction: PopoverDirection.topWithLeftAligned,
onClose: widget.onClosed,
child: Container(
@ -274,7 +274,7 @@ class _RowDetailCellState extends State<_RowDetailCell> {
offset: const Offset(20, 0),
popupBuilder: (popoverContext) {
return OverlayContainer(
constraints: BoxConstraints.loose(const Size(240, 200)),
constraints: BoxConstraints.loose(const Size(240, 600)),
child: FieldEditor(
gridId: widget.cellId.gridId,
fieldName: widget.cellId.fieldContext.field.name,

View File

@ -3,7 +3,7 @@ import 'package:app_flowy/plugins/grid/presentation/widgets/header/field_editor.
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/plugins/grid/application/setting/property_bloc.dart';
import 'package:app_flowy/plugins/grid/presentation/widgets/header/field_type_extension.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -118,7 +118,7 @@ class _GridPropertyCell extends StatelessWidget {
Widget _editFieldButton(AppTheme theme, BuildContext context) {
return AppFlowyPopover(
mutex: popoverMutex,
triggerActions: PopoverTriggerActionFlags.click,
triggerActions: PopoverTriggerFlags.click,
offset: const Offset(20, 0),
constraints: BoxConstraints.loose(const Size(240, 200)),
child: FlowyButton(

View File

@ -1,4 +1,4 @@
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:app_flowy/plugins/grid/application/setting/setting_bloc.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
@ -55,7 +55,7 @@ class _SettingButton extends StatelessWidget {
final theme = context.watch<AppTheme>();
return AppFlowyPopover(
constraints: BoxConstraints.loose(const Size(260, 400)),
triggerActions: PopoverTriggerActionFlags.click,
triggerActions: PopoverTriggerFlags.click,
offset: const Offset(0, 10),
child: FlowyIconButton(
width: 22,

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
class PopoverMenu extends StatefulWidget {
const PopoverMenu({Key? key}) : super(key: key);
@ -41,8 +41,8 @@ class _PopoverMenuState extends State<PopoverMenu> {
decoration: null)),
),
Popover(
triggerActions: PopoverTriggerActionFlags.hover |
PopoverTriggerActionFlags.click,
triggerActions:
PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
mutex: popOverMutex,
offset: const Offset(10, 0),
popupBuilder: (BuildContext context) {
@ -54,8 +54,8 @@ class _PopoverMenuState extends State<PopoverMenu> {
),
),
Popover(
triggerActions: PopoverTriggerActionFlags.hover |
PopoverTriggerActionFlags.click,
triggerActions:
PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
mutex: popOverMutex,
offset: const Offset(10, 0),
popupBuilder: (BuildContext context) {
@ -86,7 +86,7 @@ class ExampleButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Popover(
triggerActions: PopoverTriggerActionFlags.click,
triggerActions: PopoverTriggerFlags.click,
offset: offset,
direction: direction ?? PopoverDirection.rightWithTopAligned,
child: TextButton(child: Text(label), onPressed: () {}),

View File

@ -1,4 +1,4 @@
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flutter/material.dart';
import "./example_button.dart";

View File

@ -0,0 +1,5 @@
/// AppFlowyBoard library
library appflowy_popover;
export 'src/mutex.dart';
export 'src/popover.dart';

View File

@ -1,345 +0,0 @@
import 'package:appflowy_popover/layout.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
/// If multiple popovers are exclusive,
/// pass the same mutex to them.
class PopoverMutex {
final ValueNotifier<PopoverState?> _stateNotifier = ValueNotifier(null);
PopoverMutex();
void removePopoverStateListener(VoidCallback listener) {
_stateNotifier.removeListener(listener);
}
VoidCallback listenOnPopoverStateChanged(VoidCallback callback) {
listenerCallback() {
callback();
}
_stateNotifier.addListener(listenerCallback);
return listenerCallback;
}
void close() {
_stateNotifier.value?.close();
}
PopoverState? get state => _stateNotifier.value;
set state(PopoverState? newState) {
if (_stateNotifier.value != null && _stateNotifier.value != newState) {
_stateNotifier.value?.close();
}
_stateNotifier.value = newState;
}
void _removeState() {
_stateNotifier.value = null;
}
void dispose() {
_stateNotifier.dispose();
}
}
class PopoverController {
PopoverState? state;
close() {
state?.close();
}
show() {
state?.showOverlay();
}
}
class PopoverTriggerActionFlags {
static int click = 0x01;
static int hover = 0x02;
}
enum PopoverDirection {
// Corner aligned with a corner of the SourceWidget
topLeft,
topRight,
bottomLeft,
bottomRight,
center,
// Edge aligned with a edge of the SourceWidget
topWithLeftAligned,
topWithCenterAligned,
topWithRightAligned,
rightWithTopAligned,
rightWithCenterAligned,
rightWithBottomAligned,
bottomWithLeftAligned,
bottomWithCenterAligned,
bottomWithRightAligned,
leftWithTopAligned,
leftWithCenterAligned,
leftWithBottomAligned,
custom,
}
class Popover extends StatefulWidget {
final PopoverController? controller;
final Offset? offset;
final Decoration? maskDecoration;
/// The function used to build the popover.
final Widget? Function(BuildContext context) popupBuilder;
final int triggerActions;
/// If multiple popovers are exclusive,
/// pass the same mutex to them.
final PopoverMutex? mutex;
/// The direction of the popover
final PopoverDirection direction;
final void Function()? onClose;
/// The content area of the popover.
final Widget child;
const Popover({
Key? key,
required this.child,
required this.popupBuilder,
this.controller,
this.offset,
this.maskDecoration,
this.triggerActions = 0,
this.direction = PopoverDirection.rightWithTopAligned,
this.mutex,
this.onClose,
}) : super(key: key);
@override
State<Popover> createState() => PopoverState();
}
class PopoverState extends State<Popover> {
final PopoverLink popoverLink = PopoverLink();
OverlayEntry? _overlayEntry;
bool hasMask = true;
static PopoverState? _popoverWithMask;
@override
void initState() {
widget.controller?.state = this;
super.initState();
}
showOverlay() {
close();
if (widget.mutex != null) {
widget.mutex?.state = this;
}
if (_popoverWithMask == null) {
_popoverWithMask = this;
} else {
hasMask = false;
}
final newEntry = OverlayEntry(builder: (context) {
final children = <Widget>[];
if (hasMask) {
children.add(_PopoverMask(
decoration: widget.maskDecoration,
onTap: () => close(),
onExit: () => close(),
));
}
children.add(PopoverContainer(
direction: widget.direction,
popoverLink: popoverLink,
offset: widget.offset ?? Offset.zero,
popupBuilder: widget.popupBuilder,
onClose: () => close(),
onCloseAll: () => closeAll(),
));
return Stack(children: children);
});
_overlayEntry = newEntry;
Overlay.of(context)?.insert(newEntry);
}
close() {
if (_overlayEntry != null) {
_overlayEntry!.remove();
_overlayEntry = null;
if (_popoverWithMask == this) {
_popoverWithMask = null;
}
if (widget.onClose != null) {
widget.onClose!();
}
}
if (widget.mutex?.state == this) {
widget.mutex?._removeState();
}
}
closeAll() {
_popoverWithMask?.close();
}
@override
void deactivate() {
close();
super.deactivate();
}
_handleTargetPointerDown(PointerDownEvent event) {
if (widget.triggerActions & PopoverTriggerActionFlags.click != 0) {
showOverlay();
}
}
_handleTargetPointerEnter(PointerEnterEvent event) {
if (widget.triggerActions & PopoverTriggerActionFlags.hover != 0) {
showOverlay();
}
}
_buildContent(BuildContext context) {
if (widget.triggerActions == 0) {
return widget.child;
}
return MouseRegion(
onEnter: _handleTargetPointerEnter,
child: Listener(
onPointerDown: _handleTargetPointerDown,
child: widget.child,
),
);
}
@override
Widget build(BuildContext context) {
return PopoverTarget(
link: popoverLink,
child: _buildContent(context),
);
}
}
class _PopoverMask extends StatefulWidget {
final void Function() onTap;
final void Function()? onExit;
final Decoration? decoration;
const _PopoverMask(
{Key? key, required this.onTap, this.onExit, this.decoration})
: super(key: key);
@override
State<StatefulWidget> createState() => _PopoverMaskState();
}
class _PopoverMaskState extends State<_PopoverMask> {
@override
void initState() {
HardwareKeyboard.instance.addHandler(_handleGlobalKeyEvent);
super.initState();
}
bool _handleGlobalKeyEvent(KeyEvent event) {
if (event.logicalKey == LogicalKeyboardKey.escape) {
if (widget.onExit != null) {
widget.onExit!();
}
return true;
}
return false;
}
@override
void deactivate() {
HardwareKeyboard.instance.removeHandler(_handleGlobalKeyEvent);
super.deactivate();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: widget.onTap,
child: Container(
// decoration: widget.decoration,
decoration: widget.decoration ??
const BoxDecoration(
color: Color.fromARGB(0, 244, 67, 54),
),
),
);
}
}
class PopoverContainer extends StatefulWidget {
final Widget? Function(BuildContext context) popupBuilder;
final PopoverDirection direction;
final PopoverLink popoverLink;
final Offset offset;
final void Function() onClose;
final void Function() onCloseAll;
const PopoverContainer({
Key? key,
required this.popupBuilder,
required this.direction,
required this.popoverLink,
required this.offset,
required this.onClose,
required this.onCloseAll,
}) : super(key: key);
@override
State<StatefulWidget> createState() => PopoverContainerState();
static PopoverContainerState of(BuildContext context) {
if (context is StatefulElement && context.state is PopoverContainerState) {
return context.state as PopoverContainerState;
}
final PopoverContainerState? result =
context.findAncestorStateOfType<PopoverContainerState>();
return result!;
}
}
class PopoverContainerState extends State<PopoverContainer> {
@override
Widget build(BuildContext context) {
return CustomSingleChildLayout(
delegate: PopoverLayoutDelegate(
direction: widget.direction,
link: widget.popoverLink,
offset: widget.offset,
),
child: widget.popupBuilder(context),
);
}
close() => widget.onClose();
closeAll() => widget.onCloseAll();
}

View File

@ -0,0 +1,116 @@
import 'dart:collection';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
typedef EntryMap = LinkedHashMap<PopoverState, OverlayEntryContext>;
class RootOverlayEntry {
final EntryMap _entries = EntryMap();
RootOverlayEntry();
void addEntry(
BuildContext context,
PopoverState newState,
OverlayEntry entry,
bool asBarrier,
) {
_entries[newState] = OverlayEntryContext(entry, newState, asBarrier);
Overlay.of(context)?.insert(entry);
}
bool contains(PopoverState oldState) {
return _entries.containsKey(oldState);
}
void removeEntry(PopoverState oldState) {
if (_entries.isEmpty) return;
final removedEntry = _entries.remove(oldState);
removedEntry?.overlayEntry.remove();
}
bool get isEmpty => _entries.isEmpty;
bool get isNotEmpty => _entries.isNotEmpty;
bool hasEntry() {
return _entries.isNotEmpty;
}
PopoverState? popEntry() {
if (_entries.isEmpty) return null;
final lastEntry = _entries.values.last;
_entries.remove(lastEntry.popoverState);
lastEntry.overlayEntry.remove();
lastEntry.popoverState.widget.onClose?.call();
if (lastEntry.asBarrier) {
return lastEntry.popoverState;
} else {
return popEntry();
}
}
}
class OverlayEntryContext {
final bool asBarrier;
final PopoverState popoverState;
final OverlayEntry overlayEntry;
OverlayEntryContext(
this.overlayEntry,
this.popoverState,
this.asBarrier,
);
}
class PopoverMask extends StatefulWidget {
final void Function() onTap;
final void Function()? onExit;
final Decoration? decoration;
const PopoverMask(
{Key? key, required this.onTap, this.onExit, this.decoration})
: super(key: key);
@override
State<StatefulWidget> createState() => _PopoverMaskState();
}
class _PopoverMaskState extends State<PopoverMask> {
@override
void initState() {
HardwareKeyboard.instance.addHandler(_handleGlobalKeyEvent);
super.initState();
}
bool _handleGlobalKeyEvent(KeyEvent event) {
if (event.logicalKey == LogicalKeyboardKey.escape) {
if (widget.onExit != null) {
widget.onExit!();
}
return true;
} else {
return false;
}
}
@override
void deactivate() {
HardwareKeyboard.instance.removeHandler(_handleGlobalKeyEvent);
super.deactivate();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: widget.onTap,
child: Container(
decoration: widget.decoration,
),
);
}
}

View File

@ -0,0 +1,44 @@
import 'package:flutter/material.dart';
import 'popover.dart';
/// If multiple popovers are exclusive,
/// pass the same mutex to them.
class PopoverMutex {
final ValueNotifier<PopoverState?> _stateNotifier = ValueNotifier(null);
PopoverMutex();
void removePopoverStateListener(VoidCallback listener) {
_stateNotifier.removeListener(listener);
}
VoidCallback listenOnPopoverStateChanged(VoidCallback callback) {
listenerCallback() {
callback();
}
_stateNotifier.addListener(listenerCallback);
return listenerCallback;
}
void close() {
_stateNotifier.value?.close();
}
PopoverState? get state => _stateNotifier.value;
set state(PopoverState? newState) {
if (_stateNotifier.value != null && _stateNotifier.value != newState) {
_stateNotifier.value?.close();
}
_stateNotifier.value = newState;
}
void removeState() {
_stateNotifier.value = null;
}
void dispose() {
_stateNotifier.dispose();
}
}

View File

@ -0,0 +1,248 @@
import 'dart:async';
import 'package:appflowy_popover/src/layout.dart';
import 'package:flutter/material.dart';
import 'mask.dart';
import 'mutex.dart';
class PopoverController {
PopoverState? _state;
close() {
_state?.close();
}
show() {
_state?.showOverlay();
}
}
class PopoverTriggerFlags {
static int click = 0x01;
static int hover = 0x02;
}
enum PopoverDirection {
// Corner aligned with a corner of the SourceWidget
topLeft,
topRight,
bottomLeft,
bottomRight,
center,
// Edge aligned with a edge of the SourceWidget
topWithLeftAligned,
topWithCenterAligned,
topWithRightAligned,
rightWithTopAligned,
rightWithCenterAligned,
rightWithBottomAligned,
bottomWithLeftAligned,
bottomWithCenterAligned,
bottomWithRightAligned,
leftWithTopAligned,
leftWithCenterAligned,
leftWithBottomAligned,
custom,
}
class Popover extends StatefulWidget {
final PopoverController? controller;
final Offset? offset;
final Decoration? maskDecoration;
/// The function used to build the popover.
final Widget? Function(BuildContext context) popupBuilder;
final int triggerActions;
/// If multiple popovers are exclusive,
/// pass the same mutex to them.
final PopoverMutex? mutex;
/// The direction of the popover
final PopoverDirection direction;
final void Function()? onClose;
final bool asBarrier;
/// The content area of the popover.
final Widget child;
const Popover({
Key? key,
required this.child,
required this.popupBuilder,
this.controller,
this.offset,
this.maskDecoration = const BoxDecoration(
color: Color.fromARGB(0, 244, 67, 54),
),
this.triggerActions = 0,
this.direction = PopoverDirection.rightWithTopAligned,
this.mutex,
this.onClose,
this.asBarrier = false,
}) : super(key: key);
@override
State<Popover> createState() => PopoverState();
}
class PopoverState extends State<Popover> {
static final RootOverlayEntry _rootEntry = RootOverlayEntry();
final PopoverLink popoverLink = PopoverLink();
Timer? _debounceEnterRegionAction;
@override
void initState() {
widget.controller?._state = this;
super.initState();
}
void showOverlay() {
close();
if (widget.mutex != null) {
widget.mutex?.state = this;
}
final shouldAddMask = _rootEntry.isEmpty;
final newEntry = OverlayEntry(builder: (context) {
final children = <Widget>[];
if (shouldAddMask) {
children.add(
PopoverMask(
decoration: widget.maskDecoration,
onTap: () => _removeRootOverlay(),
onExit: () => _removeRootOverlay(),
),
);
}
children.add(
PopoverContainer(
direction: widget.direction,
popoverLink: popoverLink,
offset: widget.offset ?? Offset.zero,
popupBuilder: widget.popupBuilder,
onClose: () => close(),
onCloseAll: () => _removeRootOverlay(),
),
);
return Stack(children: children);
});
_rootEntry.addEntry(context, this, newEntry, widget.asBarrier);
}
void close() {
if (_rootEntry.contains(this)) {
_rootEntry.removeEntry(this);
widget.onClose?.call();
}
}
void _removeRootOverlay() {
_rootEntry.popEntry();
if (widget.mutex?.state == this) {
widget.mutex?.removeState();
}
}
@override
void deactivate() {
close();
super.deactivate();
}
@override
Widget build(BuildContext context) {
return PopoverTarget(
link: popoverLink,
child: _buildChild(context),
);
}
Widget _buildChild(BuildContext context) {
if (widget.triggerActions == 0) {
return widget.child;
}
return MouseRegion(
onEnter: (event) {
_debounceEnterRegionAction =
Timer(const Duration(milliseconds: 200), () {
if (widget.triggerActions & PopoverTriggerFlags.hover != 0) {
showOverlay();
}
});
},
onExit: (event) {
_debounceEnterRegionAction?.cancel();
_debounceEnterRegionAction = null;
},
child: Listener(
child: widget.child,
onPointerDown: (PointerDownEvent event) {
if (widget.triggerActions & PopoverTriggerFlags.click != 0) {
showOverlay();
}
},
),
);
}
}
class PopoverContainer extends StatefulWidget {
final Widget? Function(BuildContext context) popupBuilder;
final PopoverDirection direction;
final PopoverLink popoverLink;
final Offset offset;
final void Function() onClose;
final void Function() onCloseAll;
const PopoverContainer({
Key? key,
required this.popupBuilder,
required this.direction,
required this.popoverLink,
required this.offset,
required this.onClose,
required this.onCloseAll,
}) : super(key: key);
@override
State<StatefulWidget> createState() => PopoverContainerState();
static PopoverContainerState of(BuildContext context) {
if (context is StatefulElement && context.state is PopoverContainerState) {
return context.state as PopoverContainerState;
}
final PopoverContainerState? result =
context.findAncestorStateOfType<PopoverContainerState>();
return result!;
}
}
class PopoverContainerState extends State<PopoverContainer> {
@override
Widget build(BuildContext context) {
return CustomSingleChildLayout(
delegate: PopoverLayoutDelegate(
direction: widget.direction,
link: widget.popoverLink,
offset: widget.offset,
),
child: widget.popupBuilder(context),
);
}
close() => widget.onClose();
closeAll() => widget.onCloseAll();
}

View File

@ -1,5 +1,5 @@
import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flutter/material.dart';
class AppFlowyPopover extends StatelessWidget {
@ -12,6 +12,7 @@ class AppFlowyPopover extends StatelessWidget {
final void Function()? onClose;
final PopoverMutex? mutex;
final Offset? offset;
final bool asBarrier;
const AppFlowyPopover({
Key? key,
@ -24,6 +25,7 @@ class AppFlowyPopover extends StatelessWidget {
this.triggerActions = 0,
this.offset,
this.controller,
this.asBarrier = false,
}) : super(key: key);
@override
@ -33,6 +35,7 @@ class AppFlowyPopover extends StatelessWidget {
onClose: onClose,
direction: direction,
mutex: mutex,
asBarrier: asBarrier,
triggerActions: triggerActions,
popupBuilder: (context) {
final child = popupBuilder(context);