diff --git a/app_flowy/lib/startup/tasks/application_task.dart b/app_flowy/lib/startup/tasks/application_task.dart index 71aae466e2..b099878f0c 100644 --- a/app_flowy/lib/startup/tasks/application_task.dart +++ b/app_flowy/lib/startup/tasks/application_task.dart @@ -20,8 +20,6 @@ class AppWidgetTask extends LaunchTask { } } -final GlobalKey _key = GlobalKey(); - 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( diff --git a/app_flowy/lib/workspace/domain/view_edit.dart b/app_flowy/lib/workspace/domain/view_edit.dart new file mode 100644 index 0000000000..7619233776 --- /dev/null +++ b/app_flowy/lib/workspace/domain/view_edit.dart @@ -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 ''; + } + } +} diff --git a/app_flowy/lib/workspace/presentation/widgets/home_top_bar.dart b/app_flowy/lib/workspace/presentation/widgets/home_top_bar.dart index 7f790c78ac..e1b895b46b 100644 --- a/app_flowy/lib/workspace/presentation/widgets/home_top_bar.dart +++ b/app_flowy/lib/workspace/presentation/widgets/home_top_bar.dart @@ -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(); } diff --git a/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/header.dart b/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/header.dart index 5630a9ec76..a8941b89c8 100644 --- a/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/header.dart +++ b/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/header.dart @@ -59,7 +59,7 @@ class MenuAppHeader extends StatelessWidget { fontSize: 12, ), )), - DisclosureButton( + AddButton( onSelected: (viewType) { context.read().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(); - 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), ); }, ); diff --git a/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/section/item.dart b/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/section/item.dart index 7d06d47401..40ba369b72 100644 --- a/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/section/item.dart +++ b/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/section/item.dart @@ -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 createState() => _ViewSectionItemState(); +} + +class _ViewSectionItemState extends State { + 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) 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) 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(); + 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), + ); + }, + ); } } diff --git a/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/section/section.dart b/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/section/section.dart index d853de58d2..d842442952 100644 --- a/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/section/section.dart +++ b/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/section/section.dart @@ -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().setSelectedView(view); final stackView = stackCtxFromView(viewCtx.view); getIt().setStack(stackView); diff --git a/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/list_overlay.dart b/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/list_overlay.dart index d14f8b49fa..3e147ded44 100644 --- a/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/list_overlay.dart +++ b/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/list_overlay.dart @@ -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, + )), ), ); } diff --git a/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart b/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart index 99893c98f0..a85867e456 100644 --- a/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart +++ b/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart @@ -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 { 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), diff --git a/app_flowy/packages/flowy_infra_ui/lib/style_widget/icon_button.dart b/app_flowy/packages/flowy_infra_ui/lib/style_widget/icon_button.dart index 0fdaadabe9..938358127f 100644 --- a/app_flowy/packages/flowy_infra_ui/lib/style_widget/icon_button.dart +++ b/app_flowy/packages/flowy_infra_ui/lib/style_widget/icon_button.dart @@ -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"), - ); - } -}