[flutter]: config view action with FlowyOverlay

This commit is contained in:
appflowy 2021-10-11 18:26:27 +08:00
parent 854eb69854
commit 3f9807ffff
9 changed files with 182 additions and 70 deletions

View File

@ -20,8 +20,6 @@ class AppWidgetTask extends LaunchTask {
}
}
final GlobalKey<FlowyOverlayState> _key = GlobalKey<FlowyOverlayState>();
class ApplicationWidget extends StatelessWidget {
final Widget child;
const ApplicationWidget({
@ -38,7 +36,7 @@ class ApplicationWidget extends StatelessWidget {
setWindowFrame(const Rect.fromLTRB(0, 0, launchWidth, launchWidth / ratio));
final theme = AppTheme.fromType(ThemeType.light);
FlowyOverlayConfig config = FlowyOverlayConfig(barrierColor: theme.bg3.withOpacity(0.3));
FlowyOverlayConfig config = FlowyOverlayConfig(barrierColor: Colors.transparent);
return Provider.value(
value: theme,
child: MaterialApp(

View File

@ -0,0 +1,17 @@
enum ViewAction {
rename,
delete,
}
extension ViewActionExtension on ViewAction {
String get name {
switch (this) {
case ViewAction.rename:
return 'rename';
case ViewAction.delete:
return 'delete';
default:
return '';
}
}
}

View File

@ -7,7 +7,6 @@ import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pbenum.dart';
import 'package:flutter/material.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'package:flowy_infra_ui/style_widget/extension.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
@ -25,7 +24,7 @@ class HomeTopBar extends StatelessWidget {
_renderNavigation(view),
const Spacer(),
_renderShareButton(),
_renderMoreButton(),
// _renderMoreButton(),
],
)
.padding(
@ -49,14 +48,6 @@ class HomeTopBar extends StatelessWidget {
);
}
Widget _renderMoreButton() {
return ViewMoreButton(
onPressed: () {
debugPrint('show more');
},
);
}
Widget _renderNavigation(HomeStackContext view) {
return const FlowyNavigation();
}

View File

@ -59,7 +59,7 @@ class MenuAppHeader extends StatelessWidget {
fontSize: 12,
),
)),
DisclosureButton(
AddButton(
onSelected: (viewType) {
context.read<AppBloc>().add(AppEvent.createView("New view", "", viewType));
},
@ -70,9 +70,9 @@ class MenuAppHeader extends StatelessWidget {
}
}
class DisclosureButton extends StatelessWidget {
class AddButton extends StatelessWidget {
final Function(ViewType) onSelected;
const DisclosureButton({
const AddButton({
Key? key,
required this.onSelected,
}) : super(key: key);
@ -82,7 +82,7 @@ class DisclosureButton extends StatelessWidget {
return FlowyIconButton(
width: 16,
onPressed: () {
DisclosureButtonActionList(
ActionList(
anchorContext: context,
onSelected: onSelected,
).show(context);
@ -92,12 +92,12 @@ class DisclosureButton extends StatelessWidget {
}
}
class DisclosureButtonActionList {
class ActionList {
final Function(ViewType) onSelected;
final BuildContext anchorContext;
final String _identifier = 'DisclosureButtonActionList';
const DisclosureButtonActionList({required this.anchorContext, required this.onSelected});
const ActionList({required this.anchorContext, required this.onSelected});
void show(BuildContext buildContext) {
final items = ViewType.values.where((element) => element != ViewType.Blank).map((ty) {
@ -134,19 +134,17 @@ class CreateItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
final config = HoverDisplayConfig(hoverColor: theme.bg3);
final config = HoverDisplayConfig(hoverColor: theme.hover);
return FlowyHover(
config: config,
builder: (context, onHover) {
return GestureDetector(
onTap: () {
onSelected(viewType);
},
onTap: () => onSelected(viewType),
child: FlowyText.medium(
viewType.name,
fontSize: 12,
).padding(horizontal: 10, vertical: 10),
).padding(horizontal: 10, vertical: 6),
);
},
);

View File

@ -1,31 +1,47 @@
import 'package:app_flowy/workspace/presentation/widgets/menu/widget/app/menu_app.dart';
import 'package:dartz/dartz.dart' as dartz;
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.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/icon_button.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart';
import 'package:flutter/material.dart';
import 'package:app_flowy/workspace/domain/image.dart';
import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:app_flowy/workspace/domain/image.dart';
import 'package:app_flowy/workspace/domain/view_edit.dart';
import 'package:app_flowy/workspace/presentation/widgets/menu/widget/app/menu_app.dart';
class ViewWidgetContext {
final View view;
ViewWidgetContext(this.view);
Key valueKey() => ValueKey("${view.id}${view.version}");
}
typedef OpenViewCallback = void Function(View);
class ViewSectionItem extends StatelessWidget {
// ignore: must_be_immutable
class ViewSectionItem extends StatefulWidget {
final ViewWidgetContext viewCtx;
final bool isSelected;
final OpenViewCallback onOpen;
ViewSectionItem({Key? key, required this.viewCtx, required this.onOpen, required this.isSelected})
: super(key: viewCtx.valueKey());
ViewSectionItem({
Key? key,
required this.viewCtx,
required this.isSelected,
required this.onOpen,
}) : super(key: viewCtx.valueKey());
@override
State<ViewSectionItem> createState() => _ViewSectionItemState();
}
class _ViewSectionItemState extends State<ViewSectionItem> {
bool isOnSelected = false;
@override
Widget build(BuildContext context) {
@ -36,6 +52,7 @@ class ViewSectionItem extends StatelessWidget {
child: FlowyHover(
config: config,
builder: (context, onHover) => _render(context, onHover, config),
isOnSelected: () => isOnSelected || widget.isSelected,
),
);
}
@ -45,25 +62,36 @@ class ViewSectionItem extends StatelessWidget {
SizedBox(
width: 16,
height: 16,
child: svgForViewType(viewCtx.view.viewType),
child: svgForViewType(widget.viewCtx.view.viewType),
),
const HSpace(6),
FlowyText.regular(
viewCtx.view.name,
widget.viewCtx.view.name,
fontSize: 12,
),
];
if (onHover) {
if (onHover || isOnSelected) {
children.add(const Spacer());
children.add(ViewMoreButton(
onPressed: () {
debugPrint('show view setting');
children.add(ViewDisclosureButton(
onTap: () {
setState(() {
isOnSelected = true;
});
},
onSelected: (selected) {
selected.fold(() => null, (action) {
debugPrint('$action.name');
});
setState(() {
isOnSelected = false;
});
},
));
}
Widget widget = Container(
return Container(
child: Row(children: children).padding(
left: MenuAppSizes.expandedPadding,
right: MenuAppSizes.expandedIconPadding,
@ -71,15 +99,100 @@ class ViewSectionItem extends StatelessWidget {
height: 24,
alignment: Alignment.centerLeft,
);
if (isSelected) {
widget = FlowyHoverBackground(child: widget, config: config);
}
return widget;
}
Function() _openView(BuildContext context) {
return () => onOpen(viewCtx.view);
return () => widget.onOpen(widget.viewCtx.view);
}
}
class ViewDisclosureButton extends StatelessWidget {
final Function(dartz.Option<ViewAction>) onSelected;
final Function() onTap;
const ViewDisclosureButton({
Key? key,
required this.onSelected,
required this.onTap,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return FlowyIconButton(
width: 16,
onPressed: () {
onTap();
ViewActionList(
anchorContext: context,
onSelected: onSelected,
).show(context);
},
icon: svg("editor/details"),
);
}
}
class ViewActionList implements FlowyOverlayDelegate {
final Function(dartz.Option<ViewAction>) onSelected;
final BuildContext anchorContext;
final String _identifier = 'ViewActionList';
const ViewActionList({required this.anchorContext, required this.onSelected});
void show(BuildContext buildContext) {
final items = ViewAction.values.map((action) {
return ActionItem(
action: action,
onSelected: (action) {
FlowyOverlay.of(buildContext).remove(_identifier);
onSelected(dartz.some(action));
});
}).toList();
// TODO: make sure the delegate of this wouldn't cause retain cycle
ListOverlay.showWithAnchor(
buildContext,
identifier: _identifier,
itemCount: items.length,
itemBuilder: (context, index) => items[index],
anchorContext: anchorContext,
anchorDirection: AnchorDirection.bottomRight,
maxWidth: 120,
maxHeight: 80,
delegate: this,
);
}
@override
void didRemove() {
onSelected(dartz.none());
}
}
class ActionItem extends StatelessWidget {
final ViewAction action;
final Function(ViewAction) onSelected;
const ActionItem({
Key? key,
required this.action,
required this.onSelected,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
final config = HoverDisplayConfig(hoverColor: theme.hover);
return FlowyHover(
config: config,
builder: (context, onHover) {
return GestureDetector(
onTap: () => onSelected(action),
child: FlowyText.medium(
action.name,
fontSize: 12,
).padding(horizontal: 10, vertical: 6),
);
},
);
}
}

View File

@ -70,7 +70,7 @@ class ViewSection extends StatelessWidget {
viewCtx: viewCtx,
isSelected: _isViewSelected(context, view.id),
onOpen: (view) {
Log.debug("Open view: $view");
Log.debug("Open: $view");
context.read<ViewSectionNotifier>().setSelectedView(view);
final stackView = stackCtxFromView(viewCtx.view);
getIt<HomeStackManager>().setStack(stackView);

View File

@ -30,12 +30,14 @@ class ListOverlay extends StatelessWidget {
BoxShadow(color: Colors.black.withOpacity(0.1), spreadRadius: 1, blurRadius: 20.0),
],
),
child: ListView.builder(
shrinkWrap: true,
itemBuilder: itemBuilder,
itemCount: itemCount,
controller: controller,
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 6),
child: ListView.builder(
shrinkWrap: true,
itemBuilder: itemBuilder,
itemCount: itemCount,
controller: controller,
)),
),
);
}

View File

@ -4,14 +4,18 @@ import 'package:flowy_infra/time/duration.dart';
typedef HoverBuilder = Widget Function(BuildContext context, bool onHover);
typedef IsOnSelected = bool Function();
class FlowyHover extends StatefulWidget {
final HoverDisplayConfig config;
final HoverBuilder builder;
final IsOnSelected? isOnSelected;
const FlowyHover({
Key? key,
required this.builder,
required this.config,
this.isOnSelected,
}) : super(key: key);
@override
@ -34,7 +38,13 @@ class _FlowyHoverState extends State<FlowyHover> {
void setOnHover(bool value) => setState(() => _onHover = value);
Widget render() {
if (_onHover) {
var showHover = _onHover;
if (showHover == false && widget.isOnSelected != null) {
showHover = widget.isOnSelected!();
}
if (showHover) {
return FlowyHoverBackground(
config: widget.config,
child: widget.builder(context, _onHover),

View File

@ -47,20 +47,3 @@ class FlowyDropdownButton extends StatelessWidget {
);
}
}
class ViewMoreButton extends StatelessWidget {
final VoidCallback? onPressed;
const ViewMoreButton({
Key? key,
this.onPressed,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return FlowyIconButton(
width: 16,
onPressed: onPressed,
icon: svg("editor/details"),
);
}
}