Browse Source

[flutter]: config view action with FlowyOverlay

appflowy 3 years ago
parent
commit
3f9807ffff

+ 1 - 3
app_flowy/lib/startup/tasks/application_task.dart

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

+ 17 - 0
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 '';
+    }
+  }
+}

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

+ 9 - 11
app_flowy/lib/workspace/presentation/widgets/menu/widget/app/header.dart

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

+ 133 - 20
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/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/hover.dart';
 import 'package:flowy_infra_ui/style_widget/icon_button.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/style_widget/text.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
 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.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
-import 'package:app_flowy/workspace/domain/image.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: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 {
 class ViewWidgetContext {
   final View view;
   final View view;
-
   ViewWidgetContext(this.view);
   ViewWidgetContext(this.view);
-
   Key valueKey() => ValueKey("${view.id}${view.version}");
   Key valueKey() => ValueKey("${view.id}${view.version}");
 }
 }
 
 
 typedef OpenViewCallback = void Function(View);
 typedef OpenViewCallback = void Function(View);
 
 
-class ViewSectionItem extends StatelessWidget {
+// ignore: must_be_immutable
+class ViewSectionItem extends StatefulWidget {
   final ViewWidgetContext viewCtx;
   final ViewWidgetContext viewCtx;
   final bool isSelected;
   final bool isSelected;
   final OpenViewCallback onOpen;
   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
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
@@ -36,6 +52,7 @@ class ViewSectionItem extends StatelessWidget {
       child: FlowyHover(
       child: FlowyHover(
         config: config,
         config: config,
         builder: (context, onHover) => _render(context, onHover, config),
         builder: (context, onHover) => _render(context, onHover, config),
+        isOnSelected: () => isOnSelected || widget.isSelected,
       ),
       ),
     );
     );
   }
   }
@@ -45,25 +62,36 @@ class ViewSectionItem extends StatelessWidget {
       SizedBox(
       SizedBox(
         width: 16,
         width: 16,
         height: 16,
         height: 16,
-        child: svgForViewType(viewCtx.view.viewType),
+        child: svgForViewType(widget.viewCtx.view.viewType),
       ),
       ),
       const HSpace(6),
       const HSpace(6),
       FlowyText.regular(
       FlowyText.regular(
-        viewCtx.view.name,
+        widget.viewCtx.view.name,
         fontSize: 12,
         fontSize: 12,
       ),
       ),
     ];
     ];
 
 
-    if (onHover) {
+    if (onHover || isOnSelected) {
       children.add(const Spacer());
       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(
       child: Row(children: children).padding(
         left: MenuAppSizes.expandedPadding,
         left: MenuAppSizes.expandedPadding,
         right: MenuAppSizes.expandedIconPadding,
         right: MenuAppSizes.expandedIconPadding,
@@ -71,15 +99,100 @@ class ViewSectionItem extends StatelessWidget {
       height: 24,
       height: 24,
       alignment: Alignment.centerLeft,
       alignment: Alignment.centerLeft,
     );
     );
+  }
 
 
-    if (isSelected) {
-      widget = FlowyHoverBackground(child: widget, config: config);
-    }
+  Function() _openView(BuildContext context) {
+    return () => widget.onOpen(widget.viewCtx.view);
+  }
+}
 
 
-    return widget;
+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"),
+    );
   }
   }
+}
 
 
-  Function() _openView(BuildContext context) {
-    return () => onOpen(viewCtx.view);
+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),
+        );
+      },
+    );
   }
   }
 }
 }

+ 1 - 1
app_flowy/lib/workspace/presentation/widgets/menu/widget/app/section/section.dart

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

+ 8 - 6
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),
             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,
+            )),
       ),
       ),
     );
     );
   }
   }

+ 11 - 1
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 HoverBuilder = Widget Function(BuildContext context, bool onHover);
 
 
+typedef IsOnSelected = bool Function();
+
 class FlowyHover extends StatefulWidget {
 class FlowyHover extends StatefulWidget {
   final HoverDisplayConfig config;
   final HoverDisplayConfig config;
   final HoverBuilder builder;
   final HoverBuilder builder;
+  final IsOnSelected? isOnSelected;
 
 
   const FlowyHover({
   const FlowyHover({
     Key? key,
     Key? key,
     required this.builder,
     required this.builder,
     required this.config,
     required this.config,
+    this.isOnSelected,
   }) : super(key: key);
   }) : super(key: key);
 
 
   @override
   @override
@@ -34,7 +38,13 @@ class _FlowyHoverState extends State<FlowyHover> {
   void setOnHover(bool value) => setState(() => _onHover = value);
   void setOnHover(bool value) => setState(() => _onHover = value);
 
 
   Widget render() {
   Widget render() {
-    if (_onHover) {
+    var showHover = _onHover;
+
+    if (showHover == false && widget.isOnSelected != null) {
+      showHover = widget.isOnSelected!();
+    }
+
+    if (showHover) {
       return FlowyHoverBackground(
       return FlowyHoverBackground(
         config: widget.config,
         config: widget.config,
         child: widget.builder(context, _onHover),
         child: widget.builder(context, _onHover),

+ 0 - 17
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"),
-    );
-  }
-}