mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-04-24 22:57:12 -04:00
chore: replace overlay with popover (#1250)
This commit is contained in:
parent
8d6e1cdaa1
commit
ca8be6ab10
11 changed files with 393 additions and 504 deletions
|
@ -68,7 +68,6 @@ class _SettingButtonState extends State<_SettingButton> {
|
||||||
child: FlowyIconButton(
|
child: FlowyIconButton(
|
||||||
hoverColor: theme.hover,
|
hoverColor: theme.hover,
|
||||||
width: 22,
|
width: 22,
|
||||||
onPressed: () {},
|
|
||||||
icon: Padding(
|
icon: Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 3.0, horizontal: 3.0),
|
padding: const EdgeInsets.symmetric(vertical: 3.0, horizontal: 3.0),
|
||||||
child: svgWidget("grid/setting/setting", color: theme.iconColor),
|
child: svgWidget("grid/setting/setting", color: theme.iconColor),
|
||||||
|
|
|
@ -11,12 +11,11 @@ import 'package:app_flowy/workspace/presentation/home/toast.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/widgets/left_bar_item.dart';
|
import 'package:app_flowy/workspace/presentation/widgets/left_bar_item.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
|
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
|
import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||||
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:clipboard/clipboard.dart';
|
import 'package:clipboard/clipboard.dart';
|
||||||
import 'package:dartz/dartz.dart' as dartz;
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra/size.dart';
|
import 'package:flowy_infra/size.dart';
|
||||||
import 'package:flowy_infra/theme.dart';
|
import 'package:flowy_infra/theme.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
|
||||||
import 'package:flowy_infra_ui/widget/rounded_button.dart';
|
import 'package:flowy_infra_ui/widget/rounded_button.dart';
|
||||||
import 'package:flowy_sdk/log.dart';
|
import 'package:flowy_sdk/log.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||||
|
@ -130,7 +129,6 @@ class DocumentShareButton extends StatelessWidget {
|
||||||
},
|
},
|
||||||
child: BlocBuilder<DocShareBloc, DocShareState>(
|
child: BlocBuilder<DocShareBloc, DocShareState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final theme = context.watch<AppTheme>();
|
|
||||||
return ChangeNotifierProvider.value(
|
return ChangeNotifierProvider.value(
|
||||||
value: Provider.of<AppearanceSetting>(context, listen: true),
|
value: Provider.of<AppearanceSetting>(context, listen: true),
|
||||||
child: Selector<AppearanceSetting, Locale>(
|
child: Selector<AppearanceSetting, Locale>(
|
||||||
|
@ -140,14 +138,7 @@ class DocumentShareButton extends StatelessWidget {
|
||||||
height: 30,
|
height: 30,
|
||||||
width: 100,
|
width: 100,
|
||||||
),
|
),
|
||||||
child: RoundedTextButton(
|
child: const ShareActionList(),
|
||||||
title: LocaleKeys.shareAction_buttonText.tr(),
|
|
||||||
fontSize: 12,
|
|
||||||
borderRadius: Corners.s6Border,
|
|
||||||
color: theme.main1,
|
|
||||||
onPressed: () =>
|
|
||||||
_showActionList(context, const Offset(0, 10)),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -171,11 +162,30 @@ class DocumentShareButton extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleExportError(FlowyError error) {}
|
void _handleExportError(FlowyError error) {}
|
||||||
|
}
|
||||||
|
|
||||||
void _showActionList(BuildContext context, Offset offset) {
|
class ShareActionList extends StatelessWidget {
|
||||||
final actionList = ShareActions(onSelected: (result) {
|
const ShareActionList({Key? key}) : super(key: key);
|
||||||
result.fold(() {}, (action) {
|
|
||||||
switch (action) {
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = context.watch<AppTheme>();
|
||||||
|
return PopoverActionList<ShareActionWrapper>(
|
||||||
|
direction: PopoverDirection.bottomWithCenterAligned,
|
||||||
|
actions: ShareAction.values
|
||||||
|
.map((action) => ShareActionWrapper(action))
|
||||||
|
.toList(),
|
||||||
|
withChild: (controller) {
|
||||||
|
return RoundedTextButton(
|
||||||
|
title: LocaleKeys.shareAction_buttonText.tr(),
|
||||||
|
fontSize: 12,
|
||||||
|
borderRadius: Corners.s6Border,
|
||||||
|
color: theme.main1,
|
||||||
|
onPressed: () => controller.show(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onSelected: (action, controller) {
|
||||||
|
switch (action.inner) {
|
||||||
case ShareAction.markdown:
|
case ShareAction.markdown:
|
||||||
context
|
context
|
||||||
.read<DocShareBloc>()
|
.read<DocShareBloc>()
|
||||||
|
@ -189,53 +199,18 @@ class DocumentShareButton extends StatelessWidget {
|
||||||
.show(context);
|
.show(context);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
controller.close();
|
||||||
});
|
},
|
||||||
actionList.show(
|
|
||||||
context,
|
|
||||||
anchorDirection: AnchorDirection.bottomWithRightAligned,
|
|
||||||
anchorOffset: offset,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ShareActions with ActionList<ShareActionWrapper>, FlowyOverlayDelegate {
|
|
||||||
final Function(dartz.Option<ShareAction>) onSelected;
|
|
||||||
final _items =
|
|
||||||
ShareAction.values.map((action) => ShareActionWrapper(action)).toList();
|
|
||||||
|
|
||||||
ShareActions({required this.onSelected});
|
|
||||||
|
|
||||||
@override
|
|
||||||
double get itemHeight => 22;
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<ShareActionWrapper> get items => _items;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void Function(dartz.Option<ShareActionWrapper> p1) get selectCallback =>
|
|
||||||
(result) {
|
|
||||||
result.fold(
|
|
||||||
() => onSelected(dartz.none()),
|
|
||||||
(wrapper) => onSelected(
|
|
||||||
dartz.some(wrapper.inner),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
@override
|
|
||||||
FlowyOverlayDelegate? get delegate => this;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didRemove() => onSelected(dartz.none());
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ShareAction {
|
enum ShareAction {
|
||||||
markdown,
|
markdown,
|
||||||
copyLink,
|
copyLink,
|
||||||
}
|
}
|
||||||
|
|
||||||
class ShareActionWrapper extends ActionItem {
|
class ShareActionWrapper extends ActionCell {
|
||||||
final ShareAction inner;
|
final ShareAction inner;
|
||||||
|
|
||||||
ShareActionWrapper(this.inner);
|
ShareActionWrapper(this.inner);
|
||||||
|
|
|
@ -1,22 +1,21 @@
|
||||||
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
|
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
|
||||||
|
import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||||
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:expandable/expandable.dart';
|
import 'package:expandable/expandable.dart';
|
||||||
import 'package:flowy_infra/icon_data.dart';
|
import 'package:flowy_infra/icon_data.dart';
|
||||||
import 'package:flowy_infra/theme.dart';
|
import 'package:flowy_infra/theme.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_sdk/protobuf/flowy-folder/app.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-folder/app.pb.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:app_flowy/workspace/application/app/app_bloc.dart';
|
import 'package:app_flowy/workspace/application/app/app_bloc.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:dartz/dartz.dart';
|
|
||||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||||
import 'package:flowy_infra/image.dart';
|
import 'package:flowy_infra/image.dart';
|
||||||
|
|
||||||
import '../menu_app.dart';
|
import '../menu_app.dart';
|
||||||
import 'add_button.dart';
|
import 'add_button.dart';
|
||||||
import 'right_click_action.dart';
|
|
||||||
|
|
||||||
class MenuAppHeader extends StatelessWidget {
|
class MenuAppHeader extends StatelessWidget {
|
||||||
final AppPB app;
|
final AppPB app;
|
||||||
|
@ -79,30 +78,23 @@ class MenuAppHeader extends StatelessWidget {
|
||||||
expandableController.toggle();
|
expandableController.toggle();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: GestureDetector(
|
child: AppActionList(onSelected: (action) {
|
||||||
behavior: HitTestBehavior.opaque,
|
switch (action) {
|
||||||
onTap: () => ExpandableController.of(context,
|
case AppDisclosureAction.rename:
|
||||||
rebuildOnChange: false, required: true)
|
NavigatorTextFieldDialog(
|
||||||
?.toggle(),
|
title: LocaleKeys.menuAppHeader_renameDialog.tr(),
|
||||||
onSecondaryTap: () {
|
value: context.read<AppBloc>().state.app.name,
|
||||||
final actionList = AppDisclosureActionSheet(
|
confirm: (newValue) {
|
||||||
onSelected: (action) => _handleAction(context, action),
|
context.read<AppBloc>().add(AppEvent.rename(newValue));
|
||||||
);
|
},
|
||||||
actionList.show(
|
).show(context);
|
||||||
context,
|
|
||||||
anchorDirection: AnchorDirection.bottomWithCenterAligned,
|
break;
|
||||||
);
|
case AppDisclosureAction.delete:
|
||||||
},
|
context.read<AppBloc>().add(const AppEvent.delete());
|
||||||
child: BlocSelector<AppBloc, AppState, AppPB>(
|
break;
|
||||||
selector: (state) => state.app,
|
}
|
||||||
builder: (context, app) => FlowyText.medium(
|
}),
|
||||||
app.name,
|
|
||||||
fontSize: 12,
|
|
||||||
color: theme.textColor,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -123,26 +115,6 @@ class MenuAppHeader extends StatelessWidget {
|
||||||
).padding(right: MenuAppSizes.headerPadding),
|
).padding(right: MenuAppSizes.headerPadding),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleAction(BuildContext context, Option<AppDisclosureAction> action) {
|
|
||||||
action.fold(() {}, (action) {
|
|
||||||
switch (action) {
|
|
||||||
case AppDisclosureAction.rename:
|
|
||||||
NavigatorTextFieldDialog(
|
|
||||||
title: LocaleKeys.menuAppHeader_renameDialog.tr(),
|
|
||||||
value: context.read<AppBloc>().state.app.name,
|
|
||||||
confirm: (newValue) {
|
|
||||||
context.read<AppBloc>().add(AppEvent.rename(newValue));
|
|
||||||
},
|
|
||||||
).show(context);
|
|
||||||
|
|
||||||
break;
|
|
||||||
case AppDisclosureAction.delete:
|
|
||||||
context.read<AppBloc>().add(const AppEvent.delete());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AppDisclosureAction {
|
enum AppDisclosureAction {
|
||||||
|
@ -169,3 +141,57 @@ extension AppDisclosureExtension on AppDisclosureAction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class AppActionList extends StatelessWidget {
|
||||||
|
final Function(AppDisclosureAction) onSelected;
|
||||||
|
const AppActionList({
|
||||||
|
required this.onSelected,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = context.read<AppTheme>();
|
||||||
|
return PopoverActionList<DisclosureActionWrapper>(
|
||||||
|
direction: PopoverDirection.bottomWithCenterAligned,
|
||||||
|
actions: AppDisclosureAction.values
|
||||||
|
.map((action) => DisclosureActionWrapper(action))
|
||||||
|
.toList(),
|
||||||
|
withChild: (controller) {
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: () => ExpandableController.of(context,
|
||||||
|
rebuildOnChange: false, required: true)
|
||||||
|
?.toggle(),
|
||||||
|
onSecondaryTap: () {
|
||||||
|
controller.show();
|
||||||
|
},
|
||||||
|
child: BlocSelector<AppBloc, AppState, AppPB>(
|
||||||
|
selector: (state) => state.app,
|
||||||
|
builder: (context, app) => FlowyText.medium(
|
||||||
|
app.name,
|
||||||
|
fontSize: 12,
|
||||||
|
color: theme.textColor,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onSelected: (action, controller) {
|
||||||
|
onSelected(action.inner);
|
||||||
|
controller.close();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DisclosureActionWrapper extends ActionCell {
|
||||||
|
final AppDisclosureAction inner;
|
||||||
|
|
||||||
|
DisclosureActionWrapper(this.inner);
|
||||||
|
@override
|
||||||
|
Widget? icon(Color iconColor) => inner.icon(iconColor);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get name => inner.name;
|
||||||
|
}
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
|
|
||||||
import 'package:dartz/dartz.dart' as dartz;
|
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'header.dart';
|
|
||||||
|
|
||||||
class AppDisclosureActionSheet
|
|
||||||
with ActionList<DisclosureActionWrapper>, FlowyOverlayDelegate {
|
|
||||||
final Function(dartz.Option<AppDisclosureAction>) onSelected;
|
|
||||||
final _items = AppDisclosureAction.values
|
|
||||||
.map((action) => DisclosureActionWrapper(action))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
AppDisclosureActionSheet({
|
|
||||||
required this.onSelected,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<DisclosureActionWrapper> get items => _items;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void Function(dartz.Option<DisclosureActionWrapper> p1) get selectCallback =>
|
|
||||||
(result) {
|
|
||||||
result.fold(
|
|
||||||
() => onSelected(dartz.none()),
|
|
||||||
(wrapper) => onSelected(
|
|
||||||
dartz.some(wrapper.inner),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
@override
|
|
||||||
FlowyOverlayDelegate? get delegate => this;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didRemove() {
|
|
||||||
onSelected(dartz.none());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DisclosureActionWrapper extends ActionItem {
|
|
||||||
final AppDisclosureAction inner;
|
|
||||||
|
|
||||||
DisclosureActionWrapper(this.inner);
|
|
||||||
@override
|
|
||||||
Widget? icon(Color iconColor) => inner.icon(iconColor);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get name => inner.name;
|
|
||||||
}
|
|
|
@ -1,130 +0,0 @@
|
||||||
import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
|
|
||||||
import 'package:dartz/dartz.dart' as dartz;
|
|
||||||
import 'package:flowy_infra/image.dart';
|
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
|
||||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
|
||||||
import 'package:flutter/gestures.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flowy_infra/theme.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
import 'item.dart';
|
|
||||||
|
|
||||||
// [[Widget: LifeCycle]]
|
|
||||||
// https://flutterbyexample.com/lesson/stateful-widget-lifecycle
|
|
||||||
class ViewDisclosureButton extends StatelessWidget
|
|
||||||
with ActionList<ViewDisclosureActionWrapper>, FlowyOverlayDelegate {
|
|
||||||
final Function() onTap;
|
|
||||||
final Function(dartz.Option<ViewDisclosureAction>) onSelected;
|
|
||||||
final _items = ViewDisclosureAction.values
|
|
||||||
.map((action) => ViewDisclosureActionWrapper(action))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
ViewDisclosureButton({
|
|
||||||
Key? key,
|
|
||||||
required this.onTap,
|
|
||||||
required this.onSelected,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final theme = context.watch<AppTheme>();
|
|
||||||
return FlowyIconButton(
|
|
||||||
iconPadding: const EdgeInsets.all(5),
|
|
||||||
width: 26,
|
|
||||||
onPressed: () {
|
|
||||||
onTap();
|
|
||||||
show(context);
|
|
||||||
},
|
|
||||||
icon: svgWidget("editor/details", color: theme.iconColor),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<ViewDisclosureActionWrapper> get items => _items;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void Function(dartz.Option<ViewDisclosureActionWrapper> p1)
|
|
||||||
get selectCallback => (result) {
|
|
||||||
result.fold(
|
|
||||||
() => onSelected(dartz.none()),
|
|
||||||
(wrapper) => onSelected(dartz.some(wrapper.inner)),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
@override
|
|
||||||
FlowyOverlayDelegate? get delegate => this;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didRemove() {
|
|
||||||
onSelected(dartz.none());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ViewDisclosureRegion extends StatelessWidget
|
|
||||||
with ActionList<ViewDisclosureActionWrapper>, FlowyOverlayDelegate {
|
|
||||||
final Widget child;
|
|
||||||
final Function() onTap;
|
|
||||||
final Function(dartz.Option<ViewDisclosureAction>) onSelected;
|
|
||||||
final _items = ViewDisclosureAction.values
|
|
||||||
.map((action) => ViewDisclosureActionWrapper(action))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
ViewDisclosureRegion(
|
|
||||||
{Key? key,
|
|
||||||
required this.onSelected,
|
|
||||||
required this.onTap,
|
|
||||||
required this.child})
|
|
||||||
: super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Listener(
|
|
||||||
onPointerDown: (event) => _handleClick(event, context),
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
FlowyOverlayDelegate? get delegate => this;
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<ViewDisclosureActionWrapper> get items => _items;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void Function(dartz.Option<ViewDisclosureActionWrapper> p1)
|
|
||||||
get selectCallback => (result) {
|
|
||||||
result.fold(
|
|
||||||
() => onSelected(dartz.none()),
|
|
||||||
(wrapper) => onSelected(dartz.some(wrapper.inner)),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didRemove() {
|
|
||||||
onSelected(dartz.none());
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleClick(PointerDownEvent event, BuildContext context) {
|
|
||||||
if (event.kind == PointerDeviceKind.mouse &&
|
|
||||||
event.buttons == kSecondaryMouseButton) {
|
|
||||||
RenderBox box = context.findRenderObject() as RenderBox;
|
|
||||||
Offset position = box.localToGlobal(Offset.zero);
|
|
||||||
double x = event.position.dx - position.dx - box.size.width;
|
|
||||||
double y = event.position.dy - position.dy - box.size.height;
|
|
||||||
onTap();
|
|
||||||
show(context, anchorOffset: Offset(x, y));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ViewDisclosureActionWrapper extends ActionItem {
|
|
||||||
final ViewDisclosureAction inner;
|
|
||||||
|
|
||||||
ViewDisclosureActionWrapper(this.inner);
|
|
||||||
@override
|
|
||||||
Widget? icon(Color iconColor) => inner.icon(iconColor);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get name => inner.name;
|
|
||||||
}
|
|
|
@ -3,7 +3,6 @@ import 'package:app_flowy/workspace/application/view/view_bloc.dart';
|
||||||
import 'package:app_flowy/workspace/application/view/view_ext.dart';
|
import 'package:app_flowy/workspace/application/view/view_ext.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/home/menu/menu.dart';
|
import 'package:app_flowy/workspace/presentation/home/menu/menu.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
|
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
|
||||||
import 'package:dartz/dartz.dart' as dartz;
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra/theme.dart';
|
import 'package:flowy_infra/theme.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||||
|
@ -16,7 +15,9 @@ import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||||
import 'package:flowy_infra/image.dart';
|
import 'package:flowy_infra/image.dart';
|
||||||
|
|
||||||
import 'disclosure_action.dart';
|
import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||||
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
|
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||||
|
|
||||||
// ignore: must_be_immutable
|
// ignore: must_be_immutable
|
||||||
class ViewSectionItem extends StatelessWidget {
|
class ViewSectionItem extends StatelessWidget {
|
||||||
|
@ -37,40 +38,41 @@ class ViewSectionItem extends StatelessWidget {
|
||||||
return MultiBlocProvider(
|
return MultiBlocProvider(
|
||||||
providers: [
|
providers: [
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (ctx) =>
|
create: (ctx) => getIt<ViewBloc>(param1: view)
|
||||||
getIt<ViewBloc>(param1: view)..add(const ViewEvent.initial())),
|
..add(
|
||||||
|
const ViewEvent.initial(),
|
||||||
|
)),
|
||||||
],
|
],
|
||||||
child: BlocBuilder<ViewBloc, ViewState>(
|
child: BlocBuilder<ViewBloc, ViewState>(
|
||||||
builder: (context, state) {
|
builder: (blocContext, state) {
|
||||||
return ViewDisclosureRegion(
|
return Padding(
|
||||||
onTap: () => context
|
padding: const EdgeInsets.symmetric(vertical: 2),
|
||||||
.read<ViewBloc>()
|
child: InkWell(
|
||||||
.add(const ViewEvent.setIsEditing(true)),
|
onTap: () => onSelected(blocContext.read<ViewBloc>().state.view),
|
||||||
onSelected: (action) {
|
child: FlowyHover(
|
||||||
context
|
style: HoverStyle(hoverColor: theme.bg3),
|
||||||
.read<ViewBloc>()
|
buildWhen: () => !state.isEditing,
|
||||||
.add(const ViewEvent.setIsEditing(false));
|
builder: (_, onHover) => _render(
|
||||||
_handleAction(context, action);
|
blocContext,
|
||||||
},
|
onHover,
|
||||||
child: Padding(
|
state,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 2),
|
theme.iconColor,
|
||||||
child: InkWell(
|
|
||||||
onTap: () => onSelected(context.read<ViewBloc>().state.view),
|
|
||||||
child: FlowyHover(
|
|
||||||
style: HoverStyle(hoverColor: theme.bg3),
|
|
||||||
builder: (_, onHover) =>
|
|
||||||
_render(context, onHover, state, theme.iconColor),
|
|
||||||
setSelected: () => state.isEditing || isSelected,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
));
|
isSelected: () => state.isEditing || isSelected,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _render(
|
Widget _render(
|
||||||
BuildContext context, bool onHover, ViewState state, Color iconColor) {
|
BuildContext blocContext,
|
||||||
|
bool onHover,
|
||||||
|
ViewState state,
|
||||||
|
Color iconColor,
|
||||||
|
) {
|
||||||
List<Widget> children = [
|
List<Widget> children = [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 16,
|
width: 16,
|
||||||
|
@ -90,11 +92,29 @@ class ViewSectionItem extends StatelessWidget {
|
||||||
if (onHover || state.isEditing) {
|
if (onHover || state.isEditing) {
|
||||||
children.add(
|
children.add(
|
||||||
ViewDisclosureButton(
|
ViewDisclosureButton(
|
||||||
onTap: () =>
|
onEdit: (isEdit) =>
|
||||||
context.read<ViewBloc>().add(const ViewEvent.setIsEditing(true)),
|
blocContext.read<ViewBloc>().add(ViewEvent.setIsEditing(isEdit)),
|
||||||
onSelected: (action) {
|
onAction: (action) {
|
||||||
context.read<ViewBloc>().add(const ViewEvent.setIsEditing(false));
|
switch (action) {
|
||||||
_handleAction(context, action);
|
case ViewDisclosureAction.rename:
|
||||||
|
NavigatorTextFieldDialog(
|
||||||
|
title: LocaleKeys.disclosureAction_rename.tr(),
|
||||||
|
value: blocContext.read<ViewBloc>().state.view.name,
|
||||||
|
confirm: (newValue) {
|
||||||
|
blocContext
|
||||||
|
.read<ViewBloc>()
|
||||||
|
.add(ViewEvent.rename(newValue));
|
||||||
|
},
|
||||||
|
).show(blocContext);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case ViewDisclosureAction.delete:
|
||||||
|
blocContext.read<ViewBloc>().add(const ViewEvent.delete());
|
||||||
|
break;
|
||||||
|
case ViewDisclosureAction.duplicate:
|
||||||
|
blocContext.read<ViewBloc>().add(const ViewEvent.duplicate());
|
||||||
|
break;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -108,30 +128,6 @@ class ViewSectionItem extends StatelessWidget {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleAction(
|
|
||||||
BuildContext context, dartz.Option<ViewDisclosureAction> action) {
|
|
||||||
action.foldRight({}, (action, previous) {
|
|
||||||
switch (action) {
|
|
||||||
case ViewDisclosureAction.rename:
|
|
||||||
NavigatorTextFieldDialog(
|
|
||||||
title: LocaleKeys.disclosureAction_rename.tr(),
|
|
||||||
value: context.read<ViewBloc>().state.view.name,
|
|
||||||
confirm: (newValue) {
|
|
||||||
context.read<ViewBloc>().add(ViewEvent.rename(newValue));
|
|
||||||
},
|
|
||||||
).show(context);
|
|
||||||
|
|
||||||
break;
|
|
||||||
case ViewDisclosureAction.delete:
|
|
||||||
context.read<ViewBloc>().add(const ViewEvent.delete());
|
|
||||||
break;
|
|
||||||
case ViewDisclosureAction.duplicate:
|
|
||||||
context.read<ViewBloc>().add(const ViewEvent.duplicate());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ViewDisclosureAction {
|
enum ViewDisclosureAction {
|
||||||
|
@ -163,3 +159,51 @@ extension ViewDisclosureExtension on ViewDisclosureAction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ViewDisclosureButton extends StatelessWidget {
|
||||||
|
final Function(bool) onEdit;
|
||||||
|
final Function(ViewDisclosureAction) onAction;
|
||||||
|
const ViewDisclosureButton({
|
||||||
|
required this.onEdit,
|
||||||
|
required this.onAction,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = context.watch<AppTheme>();
|
||||||
|
return PopoverActionList<ViewDisclosureActionWrapper>(
|
||||||
|
direction: PopoverDirection.bottomWithCenterAligned,
|
||||||
|
actions: ViewDisclosureAction.values
|
||||||
|
.map((action) => ViewDisclosureActionWrapper(action))
|
||||||
|
.toList(),
|
||||||
|
withChild: (controller) {
|
||||||
|
return FlowyIconButton(
|
||||||
|
iconPadding: const EdgeInsets.all(5),
|
||||||
|
width: 26,
|
||||||
|
icon: svgWidget("editor/details", color: theme.iconColor),
|
||||||
|
onPressed: () {
|
||||||
|
onEdit(true);
|
||||||
|
controller.show();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onSelected: (action, controller) {
|
||||||
|
onEdit(false);
|
||||||
|
onAction(action.inner);
|
||||||
|
controller.close();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ViewDisclosureActionWrapper extends ActionCell {
|
||||||
|
final ViewDisclosureAction inner;
|
||||||
|
|
||||||
|
ViewDisclosureActionWrapper(this.inner);
|
||||||
|
@override
|
||||||
|
Widget? icon(Color iconColor) => inner.icon(iconColor);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get name => inner.name;
|
||||||
|
}
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
import 'package:app_flowy/startup/tasks/rust_sdk.dart';
|
import 'package:app_flowy/startup/tasks/rust_sdk.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/home/toast.dart';
|
import 'package:app_flowy/workspace/presentation/home/toast.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
|
import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||||
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra/theme.dart';
|
import 'package:flowy_infra/theme.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.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';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:dartz/dartz.dart' as dartz;
|
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
@ -22,41 +21,59 @@ class QuestionBubble extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = context.watch<AppTheme>();
|
return const SizedBox(
|
||||||
return SizedBox(
|
|
||||||
width: 30,
|
width: 30,
|
||||||
height: 30,
|
height: 30,
|
||||||
child: FlowyTextButton(
|
child: BubbleActionList(),
|
||||||
'?',
|
);
|
||||||
tooltip: LocaleKeys.questionBubble_help.tr(),
|
}
|
||||||
fontSize: 12,
|
}
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
fillColor: theme.selector,
|
class BubbleActionList extends StatelessWidget {
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
const BubbleActionList({Key? key}) : super(key: key);
|
||||||
radius: BorderRadius.circular(10),
|
|
||||||
onPressed: () {
|
@override
|
||||||
final actionList = QuestionBubbleActionSheet(onSelected: (result) {
|
Widget build(BuildContext context) {
|
||||||
result.fold(() {}, (action) {
|
final theme = context.watch<AppTheme>();
|
||||||
switch (action) {
|
|
||||||
case BubbleAction.whatsNews:
|
final List<PopoverAction> actions = [];
|
||||||
_launchURL("https://www.appflowy.io/whatsnew");
|
actions.addAll(
|
||||||
break;
|
BubbleAction.values.map((action) => BubbleActionWrapper(action)),
|
||||||
case BubbleAction.help:
|
);
|
||||||
_launchURL("https://discord.gg/9Q2xaN37tV");
|
actions.add(FlowyVersionDescription());
|
||||||
break;
|
|
||||||
case BubbleAction.debug:
|
return PopoverActionList<PopoverAction>(
|
||||||
_DebugToast().show();
|
direction: PopoverDirection.topWithRightAligned,
|
||||||
break;
|
actions: actions,
|
||||||
}
|
withChild: (controller) {
|
||||||
});
|
return FlowyTextButton(
|
||||||
});
|
'?',
|
||||||
actionList.show(
|
tooltip: LocaleKeys.questionBubble_help.tr(),
|
||||||
context,
|
fontSize: 12,
|
||||||
anchorDirection: AnchorDirection.topWithRightAligned,
|
fontWeight: FontWeight.w600,
|
||||||
anchorOffset: const Offset(0, -10),
|
fillColor: theme.selector,
|
||||||
);
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
},
|
radius: BorderRadius.circular(10),
|
||||||
),
|
onPressed: () => controller.show(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onSelected: (action, controller) {
|
||||||
|
if (action is BubbleActionWrapper) {
|
||||||
|
switch (action.inner) {
|
||||||
|
case BubbleAction.whatsNews:
|
||||||
|
_launchURL("https://www.appflowy.io/whatsnew");
|
||||||
|
break;
|
||||||
|
case BubbleAction.help:
|
||||||
|
_launchURL("https://discord.gg/9Q2xaN37tV");
|
||||||
|
break;
|
||||||
|
case BubbleAction.debug:
|
||||||
|
_DebugToast().show();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
controller.close();
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,54 +118,9 @@ class _DebugToast {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class QuestionBubbleActionSheet
|
class FlowyVersionDescription extends CustomActionCell {
|
||||||
with ActionList<BubbleActionWrapper>, FlowyOverlayDelegate {
|
|
||||||
final Function(dartz.Option<BubbleAction>) onSelected;
|
|
||||||
final _items =
|
|
||||||
BubbleAction.values.map((action) => BubbleActionWrapper(action)).toList();
|
|
||||||
|
|
||||||
QuestionBubbleActionSheet({
|
|
||||||
required this.onSelected,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
double get itemHeight => 22;
|
Widget buildWithContext(BuildContext context) {
|
||||||
|
|
||||||
@override
|
|
||||||
List<BubbleActionWrapper> get items => _items;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void Function(dartz.Option<BubbleActionWrapper> p1) get selectCallback =>
|
|
||||||
(result) {
|
|
||||||
result.fold(
|
|
||||||
() => onSelected(dartz.none()),
|
|
||||||
(wrapper) => onSelected(
|
|
||||||
dartz.some(wrapper.inner),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
@override
|
|
||||||
FlowyOverlayDelegate? get delegate => this;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didRemove() {
|
|
||||||
onSelected(dartz.none());
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
ListOverlayFooter? get footer => ListOverlayFooter(
|
|
||||||
widget: const FlowyVersionDescription(),
|
|
||||||
height: 40,
|
|
||||||
padding: const EdgeInsets.only(top: 6),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class FlowyVersionDescription extends StatelessWidget {
|
|
||||||
const FlowyVersionDescription({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final theme = context.watch<AppTheme>();
|
final theme = context.watch<AppTheme>();
|
||||||
|
|
||||||
return FutureBuilder(
|
return FutureBuilder(
|
||||||
|
@ -165,23 +137,26 @@ class FlowyVersionDescription extends StatelessWidget {
|
||||||
String version = packageInfo.version;
|
String version = packageInfo.version;
|
||||||
String buildNumber = packageInfo.buildNumber;
|
String buildNumber = packageInfo.buildNumber;
|
||||||
|
|
||||||
return Column(
|
return SizedBox(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
height: 30,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: Column(
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
Divider(height: 1, color: theme.shader6, thickness: 1.0),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
const VSpace(6),
|
children: [
|
||||||
FlowyText(
|
Divider(height: 1, color: theme.shader6, thickness: 1.0),
|
||||||
"$appName $version.$buildNumber",
|
const VSpace(6),
|
||||||
fontSize: 12,
|
FlowyText(
|
||||||
color: theme.shader4,
|
"$appName $version.$buildNumber",
|
||||||
),
|
fontSize: 12,
|
||||||
],
|
color: theme.shader4,
|
||||||
).padding(
|
),
|
||||||
horizontal: ActionListSizes.itemHPadding + ActionListSizes.hPadding,
|
],
|
||||||
|
).padding(
|
||||||
|
horizontal: ActionListSizes.itemHPadding,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return const CircularProgressIndicator();
|
return const SizedBox(height: 30);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -190,7 +165,7 @@ class FlowyVersionDescription extends StatelessWidget {
|
||||||
|
|
||||||
enum BubbleAction { whatsNews, help, debug }
|
enum BubbleAction { whatsNews, help, debug }
|
||||||
|
|
||||||
class BubbleActionWrapper extends ActionItem {
|
class BubbleActionWrapper extends ActionCell {
|
||||||
final BubbleAction inner;
|
final BubbleAction inner;
|
||||||
|
|
||||||
BubbleActionWrapper(this.inner);
|
BubbleActionWrapper(this.inner);
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:flowy_infra/theme.dart';
|
import 'package:flowy_infra/theme.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||||
|
@ -6,66 +7,90 @@ import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:dartz/dartz.dart' as dartz;
|
|
||||||
|
|
||||||
abstract class ActionList<T extends ActionItem> {
|
class PopoverActionList<T extends PopoverAction> extends StatefulWidget {
|
||||||
List<T> get items;
|
final List<T> actions;
|
||||||
|
final Function(T, PopoverController) onSelected;
|
||||||
|
final BoxConstraints constraints;
|
||||||
|
final PopoverDirection direction;
|
||||||
|
final Widget Function(PopoverController) withChild;
|
||||||
|
|
||||||
String get identifier => toString();
|
const PopoverActionList({
|
||||||
|
required this.actions,
|
||||||
|
required this.withChild,
|
||||||
|
required this.onSelected,
|
||||||
|
this.direction = PopoverDirection.rightWithTopAligned,
|
||||||
|
this.constraints = const BoxConstraints(
|
||||||
|
minWidth: 120,
|
||||||
|
maxWidth: 360,
|
||||||
|
maxHeight: 300,
|
||||||
|
),
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
double get maxWidth => 300;
|
@override
|
||||||
|
State<PopoverActionList<T>> createState() => _PopoverActionListState<T>();
|
||||||
|
}
|
||||||
|
|
||||||
double get minWidth => 120;
|
class _PopoverActionListState<T extends PopoverAction>
|
||||||
|
extends State<PopoverActionList<T>> {
|
||||||
|
late PopoverController popoverController;
|
||||||
|
|
||||||
double get itemHeight => ActionListSizes.itemHeight;
|
@override
|
||||||
|
void initState() {
|
||||||
|
popoverController = PopoverController();
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
ListOverlayFooter? get footer => null;
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final child = widget.withChild(popoverController);
|
||||||
|
|
||||||
void Function(dartz.Option<T>) get selectCallback;
|
return AppFlowyPopover(
|
||||||
|
controller: popoverController,
|
||||||
|
constraints: widget.constraints,
|
||||||
|
direction: widget.direction,
|
||||||
|
triggerActions: PopoverTriggerFlags.none,
|
||||||
|
popupBuilder: (BuildContext popoverContext) {
|
||||||
|
final List<Widget> children = widget.actions.map((action) {
|
||||||
|
if (action is ActionCell) {
|
||||||
|
return ActionCellWidget<T>(
|
||||||
|
action: action,
|
||||||
|
itemHeight: ActionListSizes.itemHeight,
|
||||||
|
onSelected: (action) {
|
||||||
|
widget.onSelected(action, popoverController);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
final custom = action as CustomActionCell;
|
||||||
|
return custom.buildWithContext(context);
|
||||||
|
}
|
||||||
|
}).toList();
|
||||||
|
|
||||||
FlowyOverlayDelegate? get delegate;
|
return IntrinsicHeight(
|
||||||
|
child: IntrinsicWidth(
|
||||||
void show(
|
child: Column(
|
||||||
BuildContext buildContext, {
|
children: children,
|
||||||
BuildContext? anchorContext,
|
),
|
||||||
AnchorDirection anchorDirection = AnchorDirection.bottomRight,
|
),
|
||||||
Offset? anchorOffset,
|
|
||||||
}) {
|
|
||||||
ListOverlay.showWithAnchor(
|
|
||||||
buildContext,
|
|
||||||
identifier: identifier,
|
|
||||||
itemCount: items.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final action = items[index];
|
|
||||||
return ActionCell<T>(
|
|
||||||
action: action,
|
|
||||||
itemHeight: itemHeight,
|
|
||||||
onSelected: (action) {
|
|
||||||
FlowyOverlay.of(buildContext).remove(identifier);
|
|
||||||
selectCallback(dartz.some(action));
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
anchorContext: anchorContext ?? buildContext,
|
child: child,
|
||||||
anchorDirection: anchorDirection,
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
minHeight: items.length * (itemHeight + ActionListSizes.vPadding * 2),
|
|
||||||
maxHeight: items.length * (itemHeight + ActionListSizes.vPadding * 2),
|
|
||||||
maxWidth: maxWidth,
|
|
||||||
minWidth: minWidth,
|
|
||||||
),
|
|
||||||
delegate: delegate,
|
|
||||||
anchorOffset: anchorOffset,
|
|
||||||
footer: footer,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class ActionItem {
|
abstract class ActionCell extends PopoverAction {
|
||||||
Widget? icon(Color iconColor);
|
Widget? icon(Color iconColor);
|
||||||
String get name;
|
String get name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class CustomActionCell extends PopoverAction {
|
||||||
|
Widget buildWithContext(BuildContext context);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class PopoverAction {}
|
||||||
|
|
||||||
class ActionListSizes {
|
class ActionListSizes {
|
||||||
static double itemHPadding = 10;
|
static double itemHPadding = 10;
|
||||||
static double itemHeight = 20;
|
static double itemHeight = 20;
|
||||||
|
@ -73,11 +98,11 @@ class ActionListSizes {
|
||||||
static double hPadding = 10;
|
static double hPadding = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ActionCell<T extends ActionItem> extends StatelessWidget {
|
class ActionCellWidget<T extends PopoverAction> extends StatelessWidget {
|
||||||
final T action;
|
final T action;
|
||||||
final Function(T) onSelected;
|
final Function(T) onSelected;
|
||||||
final double itemHeight;
|
final double itemHeight;
|
||||||
const ActionCell({
|
const ActionCellWidget({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.action,
|
required this.action,
|
||||||
required this.onSelected,
|
required this.onSelected,
|
||||||
|
@ -86,8 +111,9 @@ class ActionCell<T extends ActionItem> extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final actionCell = action as ActionCell;
|
||||||
final theme = context.watch<AppTheme>();
|
final theme = context.watch<AppTheme>();
|
||||||
final icon = action.icon(theme.iconColor);
|
final icon = actionCell.icon(theme.iconColor);
|
||||||
|
|
||||||
return FlowyHover(
|
return FlowyHover(
|
||||||
style: HoverStyle(hoverColor: theme.hover),
|
style: HoverStyle(hoverColor: theme.hover),
|
||||||
|
@ -99,7 +125,13 @@ class ActionCell<T extends ActionItem> extends StatelessWidget {
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
if (icon != null) ...[icon, HSpace(ActionListSizes.itemHPadding)],
|
if (icon != null) ...[icon, HSpace(ActionListSizes.itemHPadding)],
|
||||||
FlowyText.medium(action.name, fontSize: 12),
|
Expanded(
|
||||||
|
child: FlowyText.medium(
|
||||||
|
actionCell.name,
|
||||||
|
fontSize: 12,
|
||||||
|
overflow: TextOverflow.visible,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
).padding(
|
).padding(
|
||||||
|
|
|
@ -11,7 +11,7 @@ class AppFlowyPopover extends StatelessWidget {
|
||||||
final Widget Function(BuildContext context) popupBuilder;
|
final Widget Function(BuildContext context) popupBuilder;
|
||||||
final PopoverDirection direction;
|
final PopoverDirection direction;
|
||||||
final int triggerActions;
|
final int triggerActions;
|
||||||
final BoxConstraints? constraints;
|
final BoxConstraints constraints;
|
||||||
final void Function()? onClose;
|
final void Function()? onClose;
|
||||||
final PopoverMutex? mutex;
|
final PopoverMutex? mutex;
|
||||||
final Offset? offset;
|
final Offset? offset;
|
||||||
|
@ -58,12 +58,12 @@ class AppFlowyPopover extends StatelessWidget {
|
||||||
|
|
||||||
class _PopoverContainer extends StatelessWidget {
|
class _PopoverContainer extends StatelessWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
final BoxConstraints? constraints;
|
final BoxConstraints constraints;
|
||||||
final EdgeInsets margin;
|
final EdgeInsets margin;
|
||||||
const _PopoverContainer({
|
const _PopoverContainer({
|
||||||
required this.child,
|
required this.child,
|
||||||
required this.margin,
|
required this.margin,
|
||||||
this.constraints,
|
required this.constraints,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@ -74,6 +74,7 @@ class _PopoverContainer extends StatelessWidget {
|
||||||
theme.surface,
|
theme.surface,
|
||||||
theme.shadowColor.withOpacity(0.15),
|
theme.shadowColor.withOpacity(0.15),
|
||||||
);
|
);
|
||||||
|
|
||||||
return Material(
|
return Material(
|
||||||
type: MaterialType.transparency,
|
type: MaterialType.transparency,
|
||||||
child: Container(
|
child: Container(
|
||||||
|
@ -81,6 +82,14 @@ class _PopoverContainer extends StatelessWidget {
|
||||||
decoration: decoration,
|
decoration: decoration,
|
||||||
constraints: constraints,
|
constraints: constraints,
|
||||||
child: child,
|
child: child,
|
||||||
|
|
||||||
|
// SingleChildScrollView(
|
||||||
|
// scrollDirection: Axis.horizontal,
|
||||||
|
// child: ConstrainedBox(
|
||||||
|
// constraints: constraints,
|
||||||
|
// child: child,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ class FlowyButton extends StatelessWidget {
|
||||||
hoverColor: hoverColor,
|
hoverColor: hoverColor,
|
||||||
),
|
),
|
||||||
onHover: onHover,
|
onHover: onHover,
|
||||||
setSelected: () => isSelected,
|
isSelected: () => isSelected,
|
||||||
builder: (context, onHover) => _render(),
|
builder: (context, onHover) => _render(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,19 +8,21 @@ class FlowyHover extends StatefulWidget {
|
||||||
final HoverStyle style;
|
final HoverStyle style;
|
||||||
final HoverBuilder? builder;
|
final HoverBuilder? builder;
|
||||||
final Widget? child;
|
final Widget? child;
|
||||||
final bool Function()? setSelected;
|
final bool Function()? isSelected;
|
||||||
final void Function(bool)? onHover;
|
final void Function(bool)? onHover;
|
||||||
final MouseCursor? cursor;
|
final MouseCursor? cursor;
|
||||||
|
final bool Function()? buildWhen;
|
||||||
|
|
||||||
const FlowyHover(
|
const FlowyHover({
|
||||||
{Key? key,
|
Key? key,
|
||||||
this.builder,
|
this.builder,
|
||||||
this.child,
|
this.child,
|
||||||
required this.style,
|
required this.style,
|
||||||
this.setSelected,
|
this.isSelected,
|
||||||
this.onHover,
|
this.onHover,
|
||||||
this.cursor})
|
this.cursor,
|
||||||
: super(key: key);
|
this.buildWhen,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<FlowyHover> createState() => _FlowyHoverState();
|
State<FlowyHover> createState() => _FlowyHoverState();
|
||||||
|
@ -35,15 +37,23 @@ class _FlowyHoverState extends State<FlowyHover> {
|
||||||
cursor: widget.cursor != null ? widget.cursor! : SystemMouseCursors.click,
|
cursor: widget.cursor != null ? widget.cursor! : SystemMouseCursors.click,
|
||||||
opaque: false,
|
opaque: false,
|
||||||
onEnter: (p) {
|
onEnter: (p) {
|
||||||
setState(() => _onHover = true);
|
if (_onHover) return;
|
||||||
if (widget.onHover != null) {
|
|
||||||
widget.onHover!(true);
|
if (widget.buildWhen?.call() ?? true) {
|
||||||
|
setState(() => _onHover = true);
|
||||||
|
if (widget.onHover != null) {
|
||||||
|
widget.onHover!(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onExit: (p) {
|
onExit: (p) {
|
||||||
setState(() => _onHover = false);
|
if (_onHover == false) return;
|
||||||
if (widget.onHover != null) {
|
|
||||||
widget.onHover!(false);
|
if (widget.buildWhen?.call() ?? true) {
|
||||||
|
setState(() => _onHover = false);
|
||||||
|
if (widget.onHover != null) {
|
||||||
|
widget.onHover!(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: renderWidget(),
|
child: renderWidget(),
|
||||||
|
@ -52,8 +62,8 @@ class _FlowyHoverState extends State<FlowyHover> {
|
||||||
|
|
||||||
Widget renderWidget() {
|
Widget renderWidget() {
|
||||||
var showHover = _onHover;
|
var showHover = _onHover;
|
||||||
if (!showHover && widget.setSelected != null) {
|
if (!showHover && widget.isSelected != null) {
|
||||||
showHover = widget.setSelected!();
|
showHover = widget.isSelected!();
|
||||||
}
|
}
|
||||||
|
|
||||||
final child = widget.child ?? widget.builder!(context, _onHover);
|
final child = widget.child ?? widget.builder!(context, _onHover);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue