浏览代码

chore: config cell accessory

appflowy 2 年之前
父节点
当前提交
9518e164b5

+ 161 - 0
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_accessory.dart

@@ -0,0 +1,161 @@
+import 'package:flowy_infra_ui/style_widget/hover.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flowy_infra/theme.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+import 'package:flowy_infra/size.dart';
+import 'package:styled_widget/styled_widget.dart';
+
+abstract class GridCellAccessory implements Widget {
+  void onTap(BuildContext context);
+}
+
+abstract class AccessoryHoverChild extends Widget {
+  const AccessoryHoverChild({Key? key}) : super(key: key);
+
+  // The hover will show if the onFocus's value is true
+  ValueNotifier<bool>? get isFocus;
+
+  List<GridCellAccessory> accessories();
+}
+
+class AccessoryHover extends StatefulWidget {
+  final AccessoryHoverChild child;
+  final EdgeInsets contentPadding;
+  const AccessoryHover({
+    required this.child,
+    this.contentPadding = EdgeInsets.zero,
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  State<AccessoryHover> createState() => _AccessoryHoverState();
+}
+
+class _AccessoryHoverState extends State<AccessoryHover> {
+  late AccessoryHoverState _hoverState;
+  VoidCallback? _listenerFn;
+
+  @override
+  void initState() {
+    _hoverState = AccessoryHoverState();
+    _listenerFn = () => _hoverState.isFocus = widget.child.isFocus?.value ?? false;
+    widget.child.isFocus?.addListener(_listenerFn!);
+
+    super.initState();
+  }
+
+  @override
+  void dispose() {
+    _hoverState.dispose();
+
+    if (_listenerFn != null) {
+      widget.child.isFocus?.removeListener(_listenerFn!);
+      _listenerFn = null;
+    }
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    List<Widget> children = [
+      const _Background(),
+      Padding(padding: widget.contentPadding, child: widget.child),
+    ];
+    final accessories = widget.child.accessories();
+    if (accessories.isNotEmpty) {
+      children.add(
+        Padding(
+          padding: const EdgeInsets.only(right: 6),
+          child: AccessoryContainer(accessories: accessories),
+        ).positioned(right: 0),
+      );
+    }
+
+    return ChangeNotifierProvider.value(
+      value: _hoverState,
+      child: MouseRegion(
+        cursor: SystemMouseCursors.click,
+        opaque: false,
+        onEnter: (p) => setState(() => _hoverState.onHover = true),
+        onExit: (p) => setState(() => _hoverState.onHover = false),
+        child: Stack(
+          fit: StackFit.loose,
+          alignment: AlignmentDirectional.center,
+          children: children,
+        ),
+      ),
+    );
+  }
+}
+
+class AccessoryHoverState extends ChangeNotifier {
+  bool _onHover = false;
+  bool _isFocus = false;
+
+  set onHover(bool value) {
+    if (_onHover != value) {
+      _onHover = value;
+      notifyListeners();
+    }
+  }
+
+  bool get onHover => _onHover;
+
+  set isFocus(bool value) {
+    if (_isFocus != value) {
+      _isFocus = value;
+      notifyListeners();
+    }
+  }
+
+  bool get isFocus => _isFocus;
+}
+
+class _Background extends StatelessWidget {
+  const _Background({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    return Consumer<AccessoryHoverState>(
+      builder: (context, state, child) {
+        if (state.onHover || state.isFocus) {
+          return FlowyHoverContainer(
+            style: HoverStyle(borderRadius: Corners.s6Border, hoverColor: theme.shader6),
+          );
+        } else {
+          return const SizedBox();
+        }
+      },
+    );
+  }
+}
+
+class AccessoryContainer extends StatelessWidget {
+  final List<GridCellAccessory> accessories;
+  const AccessoryContainer({required this.accessories, Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    final children = accessories.map((accessory) {
+      final hover = FlowyHover(
+        style: HoverStyle(hoverColor: theme.bg3, backgroundColor: theme.surface),
+        builder: (_, onHover) => Container(
+          width: 26,
+          height: 26,
+          padding: const EdgeInsets.all(3),
+          child: accessory,
+        ),
+      );
+      return GestureDetector(
+        child: hover,
+        behavior: HitTestBehavior.opaque,
+        onTap: () => accessory.onTap(context),
+      );
+    }).toList();
+
+    return Wrap(children: children, spacing: 6);
+  }
+}

+ 25 - 39
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart

@@ -1,5 +1,4 @@
 import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
-import 'package:flowy_infra_ui/style_widget/hover.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show FieldType;
 import 'package:flutter/widgets.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart';
@@ -8,6 +7,7 @@ import 'package:flutter/material.dart';
 import 'package:provider/provider.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
 import 'package:styled_widget/styled_widget.dart';
+import 'cell_accessory.dart';
 import 'checkbox_cell.dart';
 import 'date_cell/date_cell.dart';
 import 'number_cell.dart';
@@ -48,22 +48,20 @@ class BlankCell extends StatelessWidget {
   }
 }
 
-abstract class GridCellExpander implements Widget {
-  void onExpand(BuildContext context);
-}
-
-abstract class GridCellWidget implements FlowyHoverWidget {
+abstract class GridCellWidget implements AccessoryHoverChild, CellContainerFocustable {
   @override
-  final ValueNotifier<bool> onFocus = ValueNotifier<bool>(false);
+  final ValueNotifier<bool> isFocus = ValueNotifier<bool>(false);
 
-  final GridCellBeginFocusFocus beginFocus = GridCellBeginFocusFocus();
-
-  GridCellExpander? buildExpander() {
-    return null;
+  @override
+  List<GridCellAccessory> accessories() {
+    return List.empty();
   }
+
+  @override
+  final GridCellRequestBeginFocus requestBeginFocus = GridCellRequestBeginFocus();
 }
 
-class GridCellBeginFocusFocus extends ChangeNotifier {
+class GridCellRequestBeginFocus extends ChangeNotifier {
   VoidCallback? _listener;
 
   void setListener(VoidCallback listener) {
@@ -130,9 +128,14 @@ class CellStateNotifier extends ChangeNotifier {
   bool get onEnter => _onEnter;
 }
 
+abstract class CellContainerFocustable {
+  // Listen on the requestBeginFocus if the
+  GridCellRequestBeginFocus get requestBeginFocus;
+}
+
 class CellContainer extends StatelessWidget {
   final GridCellWidget child;
-  final GridCellExpander? expander;
+  final List<GridCellAccessory> accessories;
   final double width;
   final RegionStateNotifier rowStateNotifier;
   const CellContainer({
@@ -140,7 +143,7 @@ class CellContainer extends StatelessWidget {
     required this.child,
     required this.width,
     required this.rowStateNotifier,
-    this.expander,
+    this.accessories = const [],
   }) : super(key: key);
 
   @override
@@ -152,17 +155,17 @@ class CellContainer extends StatelessWidget {
         selector: (context, notifier) => notifier.isFocus,
         builder: (context, isFocus, _) {
           Widget container = Center(child: child);
-          child.onFocus.addListener(() {
-            Provider.of<CellStateNotifier>(context, listen: false).isFocus = child.onFocus.value;
+          child.isFocus.addListener(() {
+            Provider.of<CellStateNotifier>(context, listen: false).isFocus = child.isFocus.value;
           });
 
-          if (expander != null) {
-            container = CellEnterRegion(child: container, expander: expander!);
+          if (accessories.isNotEmpty) {
+            container = CellEnterRegion(child: container, accessories: accessories);
           }
 
           return GestureDetector(
             behavior: HitTestBehavior.translucent,
-            onTap: () => child.beginFocus.notify(),
+            onTap: () => child.requestBeginFocus.notify(),
             child: Container(
               constraints: BoxConstraints(maxWidth: width, minHeight: 46),
               decoration: _makeBoxDecoration(context, isFocus),
@@ -189,33 +192,17 @@ class CellContainer extends StatelessWidget {
 
 class CellEnterRegion extends StatelessWidget {
   final Widget child;
-  final GridCellExpander expander;
-  const CellEnterRegion({required this.child, required this.expander, Key? key}) : super(key: key);
+  final List<GridCellAccessory> accessories;
+  const CellEnterRegion({required this.child, required this.accessories, Key? key}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
-    final theme = context.watch<AppTheme>();
-
     return Selector<CellStateNotifier, bool>(
       selector: (context, notifier) => notifier.onEnter,
       builder: (context, onEnter, _) {
         List<Widget> children = [child];
         if (onEnter) {
-          final hover = FlowyHover(
-            style: HoverStyle(hoverColor: theme.bg3, backgroundColor: theme.surface),
-            builder: (_, onHover) => Container(
-              width: 26,
-              height: 26,
-              padding: const EdgeInsets.all(3),
-              child: expander,
-            ),
-          );
-
-          children.add(GestureDetector(
-            child: hover,
-            behavior: HitTestBehavior.opaque,
-            onTap: () => expander.onExpand(context),
-          ).positioned(right: 0));
+          children.add(AccessoryContainer(accessories: accessories).positioned(right: 0));
         }
 
         return MouseRegion(
@@ -225,7 +212,6 @@ class CellEnterRegion extends StatelessWidget {
           child: Stack(
             alignment: AlignmentDirectional.center,
             fit: StackFit.expand,
-            // alignment: AlignmentDirectional.centerEnd,
             children: children,
           ),
         );

+ 2 - 2
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart

@@ -57,13 +57,13 @@ class _CheckboxCellState extends State<CheckboxCell> {
 
   @override
   Future<void> dispose() async {
-    widget.beginFocus.removeAllListener();
+    widget.requestBeginFocus.removeAllListener();
     _cellBloc.close();
     super.dispose();
   }
 
   void _handleRequestFocus() {
-    widget.beginFocus.setListener(() {
+    widget.requestBeginFocus.setListener(() {
       _cellBloc.add(const CheckboxCellEvent.select());
     });
   }

+ 2 - 2
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_cell.dart

@@ -76,8 +76,8 @@ class _DateCellState extends State<DateCell> {
 
   void _showCalendar(BuildContext context) {
     final bloc = context.read<DateCellBloc>();
-    widget.onFocus.value = true;
-    final calendar = DateCellEditor(onDismissed: () => widget.onFocus.value = false);
+    widget.isFocus.value = true;
+    final calendar = DateCellEditor(onDismissed: () => widget.isFocus.value = false);
     calendar.show(
       context,
       cellContext: bloc.cellContext.clone(),

+ 4 - 4
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart

@@ -65,7 +65,7 @@ class _NumberCellState extends State<NumberCell> {
 
   @override
   Future<void> dispose() async {
-    widget.beginFocus.removeAllListener();
+    widget.requestBeginFocus.removeAllListener();
     _delayOperation?.cancel();
     _cellBloc.close();
     _focusNode.removeAllListener();
@@ -91,15 +91,15 @@ class _NumberCellState extends State<NumberCell> {
   }
 
   void _listenOnFocusNodeChanged() {
-    widget.onFocus.value = _focusNode.hasFocus;
+    widget.isFocus.value = _focusNode.hasFocus;
     _focusNode.setListener(() {
-      widget.onFocus.value = _focusNode.hasFocus;
+      widget.isFocus.value = _focusNode.hasFocus;
       focusChanged();
     });
   }
 
   void _handleCellRequestFocus(BuildContext context) {
-    widget.beginFocus.setListener(() {
+    widget.requestBeginFocus.setListener(() {
       if (_focusNode.hasFocus == false && _focusNode.canRequestFocus) {
         FocusScope.of(context).requestFocus(_focusNode);
       }

+ 2 - 2
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_cell.dart

@@ -59,7 +59,7 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
           return _SelectOptionCell(
               selectOptions: state.selectedOptions,
               cellStyle: widget.cellStyle,
-              onFocus: (value) => widget.onFocus.value = value,
+              onFocus: (value) => widget.isFocus.value = value,
               cellContextBuilder: widget.cellContextBuilder);
         },
       ),
@@ -113,7 +113,7 @@ class _MultiSelectCellState extends State<MultiSelectCell> {
           return _SelectOptionCell(
               selectOptions: state.selectedOptions,
               cellStyle: widget.cellStyle,
-              onFocus: (value) => widget.onFocus.value = value,
+              onFocus: (value) => widget.isFocus.value = value,
               cellContextBuilder: widget.cellContextBuilder);
         },
       ),

+ 4 - 4
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart

@@ -81,7 +81,7 @@ class _GridTextCellState extends State<GridTextCell> {
 
   @override
   Future<void> dispose() async {
-    widget.beginFocus.removeAllListener();
+    widget.requestBeginFocus.removeAllListener();
     _delayOperation?.cancel();
     _cellBloc.close();
     _focusNode.removeAllListener();
@@ -99,15 +99,15 @@ class _GridTextCellState extends State<GridTextCell> {
   }
 
   void _listenOnFocusNodeChanged() {
-    widget.onFocus.value = _focusNode.hasFocus;
+    widget.isFocus.value = _focusNode.hasFocus;
     _focusNode.setListener(() {
-      widget.onFocus.value = _focusNode.hasFocus;
+      widget.isFocus.value = _focusNode.hasFocus;
       focusChanged();
     });
   }
 
   void _listenRequestFocus(BuildContext context) {
-    widget.beginFocus.setListener(() {
+    widget.requestBeginFocus.setListener(() {
       if (_focusNode.hasFocus == false && _focusNode.canRequestFocus) {
         FocusScope.of(context).requestFocus(_focusNode);
       }

+ 7 - 1
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/cell_editor.dart

@@ -6,7 +6,7 @@ import 'dart:async';
 
 import 'package:flutter_bloc/flutter_bloc.dart';
 
-class URLCellEditor extends StatefulWidget {
+class URLCellEditor extends StatefulWidget with FlowyOverlayDelegate {
   final GridURLCellContext cellContext;
   const URLCellEditor({required this.cellContext, Key? key}) : super(key: key);
 
@@ -34,12 +34,18 @@ class URLCellEditor extends StatefulWidget {
       identifier: URLCellEditor.identifier(),
       anchorContext: context,
       anchorDirection: AnchorDirection.bottomWithCenterAligned,
+      delegate: editor,
     );
   }
 
   static String identifier() {
     return (URLCellEditor).toString();
   }
+
+  @override
+  bool asBarrier() {
+    return true;
+  }
 }
 
 class _URLCellEditorState extends State<URLCellEditor> {

+ 57 - 10
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/url_cell.dart

@@ -1,9 +1,10 @@
 import 'dart:async';
 import 'package:app_flowy/workspace/application/grid/cell/url_cell_bloc.dart';
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/cell_accessory.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
-import 'package:flutter/gestures.dart';
 import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:app_flowy/workspace/application/grid/prelude.dart';
 import 'package:url_launcher/url_launcher.dart';
@@ -13,11 +14,19 @@ import 'cell_editor.dart';
 class GridURLCellStyle extends GridCellStyle {
   String? placeholder;
 
+  List<GridURLCellAccessoryType> accessoryTypes;
+
   GridURLCellStyle({
     this.placeholder,
+    this.accessoryTypes = const [],
   });
 }
 
+enum GridURLCellAccessoryType {
+  edit,
+  copyURL,
+}
+
 class GridURLCell extends StatefulWidget with GridCellWidget {
   final GridCellContextBuilder cellContextBuilder;
   late final GridURLCellStyle? cellStyle;
@@ -37,9 +46,30 @@ class GridURLCell extends StatefulWidget with GridCellWidget {
   State<GridURLCell> createState() => _GridURLCellState();
 
   @override
-  GridCellExpander? buildExpander() {
-    final cellContext = cellContextBuilder.build() as GridURLCellContext;
-    return _EditURLCellIndicator(cellContext: cellContext);
+  List<GridCellAccessory> accessories() {
+    final List<GridCellAccessory> accessories = [];
+    if (cellStyle != null) {
+      accessories.addAll(cellStyle!.accessoryTypes.map(accessoryFromType));
+    }
+
+    // If the accessories is empty then the default accessory will be GridURLCellAccessoryType.edit
+    if (accessories.isEmpty) {
+      accessories.add(accessoryFromType(GridURLCellAccessoryType.edit));
+    }
+
+    return accessories;
+  }
+
+  GridCellAccessory accessoryFromType(GridURLCellAccessoryType ty) {
+    switch (ty) {
+      case GridURLCellAccessoryType.edit:
+        final cellContext = cellContextBuilder.build() as GridURLCellContext;
+        return _EditURLAccessory(cellContext: cellContext);
+
+      case GridURLCellAccessoryType.copyURL:
+        final cellContext = cellContextBuilder.build() as GridURLCellContext;
+        return _CopyURLAccessory(cellContext: cellContext);
+    }
   }
 }
 
@@ -78,7 +108,7 @@ class _GridURLCellState extends State<GridURLCell> {
               child: GestureDetector(
             child: Align(alignment: Alignment.centerLeft, child: richText),
             onTap: () async {
-              widget.onFocus.value = true;
+              widget.isFocus.value = true;
               final url = context.read<URLCellBloc>().state.url;
               await _openUrlOrEdit(url);
             },
@@ -90,7 +120,7 @@ class _GridURLCellState extends State<GridURLCell> {
 
   @override
   Future<void> dispose() async {
-    widget.beginFocus.removeAllListener();
+    widget.requestBeginFocus.removeAllListener();
     _cellBloc.close();
     super.dispose();
   }
@@ -112,15 +142,15 @@ class _GridURLCellState extends State<GridURLCell> {
   }
 
   void _handleRequestFocus() {
-    widget.beginFocus.setListener(() {
+    widget.requestBeginFocus.setListener(() {
       _openUrlOrEdit(_cellBloc.state.url);
     });
   }
 }
 
-class _EditURLCellIndicator extends StatelessWidget with GridCellExpander {
+class _EditURLAccessory extends StatelessWidget with GridCellAccessory {
   final GridURLCellContext cellContext;
-  const _EditURLCellIndicator({required this.cellContext, Key? key}) : super(key: key);
+  const _EditURLAccessory({required this.cellContext, Key? key}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
@@ -129,7 +159,24 @@ class _EditURLCellIndicator extends StatelessWidget with GridCellExpander {
   }
 
   @override
-  void onExpand(BuildContext context) {
+  void onTap(BuildContext context) {
     URLCellEditor.show(context, cellContext);
   }
 }
+
+class _CopyURLAccessory extends StatelessWidget with GridCellAccessory {
+  final GridURLCellContext cellContext;
+  const _CopyURLAccessory({required this.cellContext, Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    return svgWidget("editor/copy", color: theme.iconColor);
+  }
+
+  @override
+  void onTap(BuildContext context) {
+    final content = cellContext.getCellData()?.content ?? "";
+    Clipboard.setData(ClipboardData(text: content));
+  }
+}

+ 10 - 8
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart

@@ -1,5 +1,6 @@
 import 'package:app_flowy/workspace/application/grid/prelude.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/cell_accessory.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/prelude.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
@@ -171,16 +172,17 @@ class _RowCells extends StatelessWidget {
     return gridCellMap.values.map(
       (gridCell) {
         final GridCellWidget child = buildGridCellWidget(gridCell, cellCache);
-        GridCellExpander? expander = child.buildExpander();
+        List<GridCellAccessory> accessories = [];
         if (gridCell.field.isPrimary) {
-          expander = _PrimaryCellExpander(onTap: onExpand);
+          accessories.add(_PrimaryCellAccessory(onTapCallback: onExpand));
         }
+        accessories.addAll(child.accessories());
 
         return CellContainer(
           width: gridCell.field.width.toDouble(),
           child: child,
           rowStateNotifier: Provider.of<RegionStateNotifier>(context, listen: false),
-          expander: expander,
+          accessories: accessories,
         );
       },
     ).toList();
@@ -200,9 +202,9 @@ class RegionStateNotifier extends ChangeNotifier {
   bool get onEnter => _onEnter;
 }
 
-class _PrimaryCellExpander extends StatelessWidget with GridCellExpander {
-  final VoidCallback onTap;
-  const _PrimaryCellExpander({required this.onTap, Key? key}) : super(key: key);
+class _PrimaryCellAccessory extends StatelessWidget with GridCellAccessory {
+  final VoidCallback onTapCallback;
+  const _PrimaryCellAccessory({required this.onTapCallback, Key? key}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
@@ -211,8 +213,8 @@ class _PrimaryCellExpander extends StatelessWidget with GridCellExpander {
   }
 
   @override
-  void onExpand(BuildContext context) {
-    onTap();
+  void onTap(BuildContext context) {
+    onTapCallback();
   }
 }
 

+ 10 - 8
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart

@@ -3,6 +3,7 @@ import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
 import 'package:app_flowy/workspace/application/grid/row/row_detail_bloc.dart';
 import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/cell_accessory.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/prelude.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/url_cell.dart';
 import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart';
@@ -149,12 +150,9 @@ class _RowDetailCell extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
+    final style = _customCellStyle(theme, gridCell.field.fieldType);
+    final cell = buildGridCellWidget(gridCell, cellCache, style: style);
 
-    final cell = buildGridCellWidget(
-      gridCell,
-      cellCache,
-      style: _buildCellStyle(theme, gridCell.field.fieldType),
-    );
     return ConstrainedBox(
       constraints: const BoxConstraints(minHeight: 40),
       child: IntrinsicHeight(
@@ -168,7 +166,7 @@ class _RowDetailCell extends StatelessWidget {
             ),
             const HSpace(10),
             Expanded(
-              child: FlowyHover2(
+              child: AccessoryHover(
                 child: cell,
                 contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 12),
               ),
@@ -191,7 +189,7 @@ class _RowDetailCell extends StatelessWidget {
   }
 }
 
-GridCellStyle? _buildCellStyle(AppTheme theme, FieldType fieldType) {
+GridCellStyle? _customCellStyle(AppTheme theme, FieldType fieldType) {
   switch (fieldType) {
     case FieldType.Checkbox:
       return null;
@@ -217,7 +215,11 @@ GridCellStyle? _buildCellStyle(AppTheme theme, FieldType fieldType) {
     case FieldType.URL:
       return GridURLCellStyle(
         placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),
+        accessoryTypes: [
+          GridURLCellAccessoryType.edit,
+          GridURLCellAccessoryType.copyURL,
+        ],
       );
   }
-  return null;
+  throw UnimplementedError;
 }

+ 0 - 120
frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart

@@ -1,9 +1,6 @@
 import 'package:flutter/material.dart';
 // ignore: unused_import
 import 'package:flowy_infra/time/duration.dart';
-import 'package:flowy_infra/size.dart';
-import 'package:flowy_infra/theme.dart';
-import 'package:provider/provider.dart';
 
 typedef HoverBuilder = Widget Function(BuildContext context, bool onHover);
 
@@ -102,120 +99,3 @@ class FlowyHoverContainer extends StatelessWidget {
     );
   }
 }
-
-//
-abstract class FlowyHoverWidget extends Widget {
-  const FlowyHoverWidget({Key? key}) : super(key: key);
-
-  ValueNotifier<bool>? get onFocus;
-}
-
-class FlowyHover2 extends StatefulWidget {
-  final FlowyHoverWidget child;
-  final EdgeInsets contentPadding;
-  const FlowyHover2({
-    required this.child,
-    this.contentPadding = EdgeInsets.zero,
-    Key? key,
-  }) : super(key: key);
-
-  @override
-  State<FlowyHover2> createState() => _FlowyHover2State();
-}
-
-class _FlowyHover2State extends State<FlowyHover2> {
-  late FlowyHoverState _hoverState;
-  VoidCallback? _listenerFn;
-
-  @override
-  void initState() {
-    _hoverState = FlowyHoverState();
-
-    listener() {
-      _hoverState.onFocus = widget.child.onFocus?.value ?? false;
-    }
-
-    _listenerFn = listener;
-    widget.child.onFocus?.addListener(listener);
-
-    super.initState();
-  }
-
-  @override
-  void dispose() {
-    _hoverState.dispose();
-
-    if (_listenerFn != null) {
-      widget.child.onFocus?.removeListener(_listenerFn!);
-      _listenerFn = null;
-    }
-    super.dispose();
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    return ChangeNotifierProvider.value(
-      value: _hoverState,
-      child: MouseRegion(
-        cursor: SystemMouseCursors.click,
-        opaque: false,
-        onEnter: (p) => setState(() => _hoverState.onHover = true),
-        onExit: (p) => setState(() => _hoverState.onHover = false),
-        child: Stack(
-          fit: StackFit.loose,
-          alignment: AlignmentDirectional.center,
-          children: [
-            const _HoverBackground(),
-            Padding(
-              padding: widget.contentPadding,
-              child: widget.child,
-            ),
-          ],
-        ),
-      ),
-    );
-  }
-}
-
-class _HoverBackground extends StatelessWidget {
-  const _HoverBackground({Key? key}) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    final theme = context.watch<AppTheme>();
-    return Consumer<FlowyHoverState>(
-      builder: (context, state, child) {
-        if (state.onHover || state.onFocus) {
-          return FlowyHoverContainer(
-            style: HoverStyle(borderRadius: Corners.s6Border, hoverColor: theme.shader6),
-          );
-        } else {
-          return const SizedBox();
-        }
-      },
-    );
-  }
-}
-
-class FlowyHoverState extends ChangeNotifier {
-  bool _onHover = false;
-  bool _onFocus = false;
-
-  set onHover(bool value) {
-    if (_onHover != value) {
-      _onHover = value;
-      notifyListeners();
-    }
-  }
-
-  bool get onHover => _onHover;
-
-  set onFocus(bool value) {
-    if (_onFocus != value) {
-      _onFocus = value;
-      notifyListeners();
-    }
-  }
-
-  bool get onFocus => _onFocus;
-}