瀏覽代碼

Merge pull request #534 from AppFlowy-IO/fix/grid_cell_highlight

chore: hide accessory when cell is editing
Nathan.fooo 2 年之前
父節點
當前提交
59a1b0d869

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

@@ -1,3 +1,4 @@
+import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra_ui/style_widget/hover.dart';
 import 'package:flutter/widgets.dart';
 import 'package:flowy_infra/theme.dart';
@@ -8,12 +9,45 @@ import 'package:styled_widget/styled_widget.dart';
 
 class GridCellAccessoryBuildContext {
   final BuildContext anchorContext;
+  final bool isCellEditing;
 
-  GridCellAccessoryBuildContext({required this.anchorContext});
+  GridCellAccessoryBuildContext({
+    required this.anchorContext,
+    required this.isCellEditing,
+  });
 }
 
 abstract class GridCellAccessory implements Widget {
   void onTap();
+
+  // The accessory will be hidden if enable() return false;
+  bool enable() => true;
+}
+
+class PrimaryCellAccessory extends StatelessWidget with GridCellAccessory {
+  final VoidCallback onTapCallback;
+  final bool isCellEditing;
+  const PrimaryCellAccessory({
+    required this.onTapCallback,
+    required this.isCellEditing,
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    if (isCellEditing) {
+      return const SizedBox();
+    } else {
+      final theme = context.watch<AppTheme>();
+      return svgWidget("grid/expander", color: theme.main1);
+    }
+  }
+
+  @override
+  void onTap() => onTapCallback();
+
+  @override
+  bool enable() => !isCellEditing;
 }
 
 typedef AccessoryBuilder = List<GridCellAccessory> Function(GridCellAccessoryBuildContext buildContext);
@@ -21,8 +55,8 @@ typedef AccessoryBuilder = List<GridCellAccessory> Function(GridCellAccessoryBui
 abstract class CellAccessory extends Widget {
   const CellAccessory({Key? key}) : super(key: key);
 
-  // The hover will show if the onFocus's value is true
-  ValueNotifier<bool>? get isFocus;
+  // The hover will show if the isHover's value is true
+  ValueNotifier<bool>? get onAccessoryHover;
 
   AccessoryBuilder? get accessoryBuilder;
 }
@@ -47,8 +81,8 @@ class _AccessoryHoverState extends State<AccessoryHover> {
   @override
   void initState() {
     _hoverState = AccessoryHoverState();
-    _listenerFn = () => _hoverState.isFocus = widget.child.isFocus?.value ?? false;
-    widget.child.isFocus?.addListener(_listenerFn!);
+    _listenerFn = () => _hoverState.onHover = widget.child.onAccessoryHover?.value ?? false;
+    widget.child.onAccessoryHover?.addListener(_listenerFn!);
 
     super.initState();
   }
@@ -58,7 +92,7 @@ class _AccessoryHoverState extends State<AccessoryHover> {
     _hoverState.dispose();
 
     if (_listenerFn != null) {
-      widget.child.isFocus?.removeListener(_listenerFn!);
+      widget.child.onAccessoryHover?.removeListener(_listenerFn!);
       _listenerFn = null;
     }
     super.dispose();
@@ -73,11 +107,14 @@ class _AccessoryHoverState extends State<AccessoryHover> {
 
     final accessoryBuilder = widget.child.accessoryBuilder;
     if (accessoryBuilder != null) {
-      final accessories = accessoryBuilder((GridCellAccessoryBuildContext(anchorContext: context)));
+      final accessories = accessoryBuilder((GridCellAccessoryBuildContext(
+        anchorContext: context,
+        isCellEditing: false,
+      )));
       children.add(
         Padding(
           padding: const EdgeInsets.only(right: 6),
-          child: AccessoryContainer(accessories: accessories),
+          child: CellAccessoryContainer(accessories: accessories),
         ).positioned(right: 0),
       );
     }
@@ -101,7 +138,6 @@ class _AccessoryHoverState extends State<AccessoryHover> {
 
 class AccessoryHoverState extends ChangeNotifier {
   bool _onHover = false;
-  bool _isFocus = false;
 
   set onHover(bool value) {
     if (_onHover != value) {
@@ -111,15 +147,6 @@ class AccessoryHoverState extends ChangeNotifier {
   }
 
   bool get onHover => _onHover;
-
-  set isFocus(bool value) {
-    if (_isFocus != value) {
-      _isFocus = value;
-      notifyListeners();
-    }
-  }
-
-  bool get isFocus => _isFocus;
 }
 
 class _Background extends StatelessWidget {
@@ -130,7 +157,7 @@ class _Background extends StatelessWidget {
     final theme = context.watch<AppTheme>();
     return Consumer<AccessoryHoverState>(
       builder: (context, state, child) {
-        if (state.onHover || state.isFocus) {
+        if (state.onHover) {
           return FlowyHoverContainer(
             style: HoverStyle(borderRadius: Corners.s6Border, hoverColor: theme.shader6),
           );
@@ -142,14 +169,14 @@ class _Background extends StatelessWidget {
   }
 }
 
-class AccessoryContainer extends StatelessWidget {
+class CellAccessoryContainer extends StatelessWidget {
   final List<GridCellAccessory> accessories;
-  const AccessoryContainer({required this.accessories, Key? key}) : super(key: key);
+  const CellAccessoryContainer({required this.accessories, Key? key}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
-    final children = accessories.map((accessory) {
+    final children = accessories.where((accessory) => accessory.enable()).map((accessory) {
       final hover = FlowyHover(
         style: HoverStyle(hoverColor: theme.bg3, backgroundColor: theme.surface),
         builder: (_, onHover) => Container(

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

@@ -2,12 +2,7 @@ import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_serv
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show FieldType;
 import 'package:flutter/services.dart';
 import 'package:flutter/widgets.dart';
-import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart';
-import 'package:flowy_infra/theme.dart';
 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 'cell_shortcuts.dart';
 import 'checkbox_cell.dart';
@@ -50,11 +45,30 @@ class BlankCell extends StatelessWidget {
   }
 }
 
-abstract class GridCellWidget extends StatefulWidget implements CellAccessory, CellFocustable, CellShortcuts {
-  GridCellWidget({Key? key}) : super(key: key);
+abstract class CellEditable {
+  GridCellFocusListener get beginFocus;
+
+  ValueNotifier<bool> get onCellFocus;
+
+  ValueNotifier<bool> get onCellEditing;
+}
+
+abstract class GridCellWidget extends StatefulWidget implements CellAccessory, CellEditable, CellShortcuts {
+  GridCellWidget({Key? key}) : super(key: key) {
+    onCellEditing.addListener(() {
+      onCellFocus.value = onCellEditing.value;
+    });
+  }
+
+  @override
+  final ValueNotifier<bool> onCellFocus = ValueNotifier<bool>(false);
+
+  // When the cell is focused, we assume that the accessory alse be hovered.
+  @override
+  ValueNotifier<bool> get onAccessoryHover => onCellFocus;
 
   @override
-  final ValueNotifier<bool> isFocus = ValueNotifier<bool>(false);
+  final ValueNotifier<bool> onCellEditing = ValueNotifier<bool>(false);
 
   @override
   List<GridCellAccessory> Function(GridCellAccessoryBuildContext buildContext)? get accessoryBuilder => null;
@@ -137,9 +151,9 @@ abstract class GridFocusNodeCellState<T extends GridCellWidget> extends GridCell
   }
 
   void _listenOnFocusNodeChanged() {
-    widget.isFocus.value = focusNode.hasFocus;
+    widget.onCellEditing.value = focusNode.hasFocus;
     focusNode.setListener(() {
-      widget.isFocus.value = focusNode.hasFocus;
+      widget.onCellEditing.value = focusNode.hasFocus;
       focusChanged();
     });
   }
@@ -190,121 +204,3 @@ class SingleListenrFocusNode extends FocusNode {
     }
   }
 }
-
-class CellStateNotifier extends ChangeNotifier {
-  bool _isFocus = false;
-  bool _onEnter = false;
-
-  set isFocus(bool value) {
-    if (_isFocus != value) {
-      _isFocus = value;
-      notifyListeners();
-    }
-  }
-
-  set onEnter(bool value) {
-    if (_onEnter != value) {
-      _onEnter = value;
-      notifyListeners();
-    }
-  }
-
-  bool get isFocus => _isFocus;
-
-  bool get onEnter => _onEnter;
-}
-
-abstract class CellFocustable {
-  GridCellFocusListener get beginFocus;
-}
-
-class CellContainer extends StatelessWidget {
-  final GridCellWidget child;
-  final AccessoryBuilder? accessoryBuilder;
-  final double width;
-  final RegionStateNotifier rowStateNotifier;
-  const CellContainer({
-    Key? key,
-    required this.child,
-    required this.width,
-    required this.rowStateNotifier,
-    this.accessoryBuilder,
-  }) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    return ChangeNotifierProxyProvider<RegionStateNotifier, CellStateNotifier>(
-      create: (_) => CellStateNotifier(),
-      update: (_, row, cell) => cell!..onEnter = row.onEnter,
-      child: Selector<CellStateNotifier, bool>(
-        selector: (context, notifier) => notifier.isFocus,
-        builder: (context, isFocus, _) {
-          Widget container = Center(child: GridCellShortcuts(child: child));
-          child.isFocus.addListener(() {
-            Provider.of<CellStateNotifier>(context, listen: false).isFocus = child.isFocus.value;
-          });
-
-          if (accessoryBuilder != null) {
-            final buildContext = GridCellAccessoryBuildContext(anchorContext: context);
-            final accessories = accessoryBuilder!(buildContext);
-            if (accessories.isNotEmpty) {
-              container = CellEnterRegion(child: container, accessories: accessories);
-            }
-          }
-
-          return GestureDetector(
-            behavior: HitTestBehavior.translucent,
-            onTap: () => child.beginFocus.notify(),
-            child: Container(
-              constraints: BoxConstraints(maxWidth: width, minHeight: 46),
-              decoration: _makeBoxDecoration(context, isFocus),
-              padding: GridSize.cellContentInsets,
-              child: container,
-            ),
-          );
-        },
-      ),
-    );
-  }
-
-  BoxDecoration _makeBoxDecoration(BuildContext context, bool isFocus) {
-    final theme = context.watch<AppTheme>();
-    if (isFocus) {
-      final borderSide = BorderSide(color: theme.main1, width: 1.0);
-      return BoxDecoration(border: Border.fromBorderSide(borderSide));
-    } else {
-      final borderSide = BorderSide(color: theme.shader5, width: 1.0);
-      return BoxDecoration(border: Border(right: borderSide, bottom: borderSide));
-    }
-  }
-}
-
-class CellEnterRegion extends StatelessWidget {
-  final Widget child;
-  final List<GridCellAccessory> accessories;
-  const CellEnterRegion({required this.child, required this.accessories, Key? key}) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    return Selector<CellStateNotifier, bool>(
-      selector: (context, notifier) => notifier.onEnter,
-      builder: (context, onEnter, _) {
-        List<Widget> children = [child];
-        if (onEnter) {
-          children.add(AccessoryContainer(accessories: accessories).positioned(right: 0));
-        }
-
-        return MouseRegion(
-          cursor: SystemMouseCursors.click,
-          onEnter: (p) => Provider.of<CellStateNotifier>(context, listen: false).onEnter = true,
-          onExit: (p) => Provider.of<CellStateNotifier>(context, listen: false).onEnter = false,
-          child: Stack(
-            alignment: AlignmentDirectional.center,
-            fit: StackFit.expand,
-            children: children,
-          ),
-        );
-      },
-    );
-  }
-}

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

@@ -0,0 +1,140 @@
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
+import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart';
+import 'package:flowy_infra/theme.dart';
+import 'package:flutter/widgets.dart';
+import 'package:provider/provider.dart';
+import 'package:styled_widget/styled_widget.dart';
+
+import 'cell_accessory.dart';
+import 'cell_builder.dart';
+import 'cell_shortcuts.dart';
+
+class CellContainer extends StatelessWidget {
+  final GridCellWidget child;
+  final AccessoryBuilder? accessoryBuilder;
+  final double width;
+  final RegionStateNotifier rowStateNotifier;
+  const CellContainer({
+    Key? key,
+    required this.child,
+    required this.width,
+    required this.rowStateNotifier,
+    this.accessoryBuilder,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return ChangeNotifierProxyProvider<RegionStateNotifier, CellContainerNotifier>(
+      create: (_) => CellContainerNotifier(child),
+      update: (_, rowStateNotifier, cellStateNotifier) => cellStateNotifier!..onEnter = rowStateNotifier.onEnter,
+      child: Selector<CellContainerNotifier, bool>(
+        selector: (context, notifier) => notifier.isFocus,
+        builder: (context, isFocus, _) {
+          Widget container = Center(child: GridCellShortcuts(child: child));
+
+          if (accessoryBuilder != null) {
+            final accessories = accessoryBuilder!(GridCellAccessoryBuildContext(
+              anchorContext: context,
+              isCellEditing: isFocus,
+            ));
+
+            if (accessories.isNotEmpty) {
+              container = CellEnterRegion(child: container, accessories: accessories);
+            }
+          }
+
+          return GestureDetector(
+            behavior: HitTestBehavior.translucent,
+            onTap: () => child.beginFocus.notify(),
+            child: Container(
+              constraints: BoxConstraints(maxWidth: width, minHeight: 46),
+              decoration: _makeBoxDecoration(context, isFocus),
+              padding: GridSize.cellContentInsets,
+              child: container,
+            ),
+          );
+        },
+      ),
+    );
+  }
+
+  BoxDecoration _makeBoxDecoration(BuildContext context, bool isFocus) {
+    final theme = context.watch<AppTheme>();
+    if (isFocus) {
+      final borderSide = BorderSide(color: theme.main1, width: 1.0);
+      return BoxDecoration(border: Border.fromBorderSide(borderSide));
+    } else {
+      final borderSide = BorderSide(color: theme.shader5, width: 1.0);
+      return BoxDecoration(border: Border(right: borderSide, bottom: borderSide));
+    }
+  }
+}
+
+class CellEnterRegion extends StatelessWidget {
+  final Widget child;
+  final List<GridCellAccessory> accessories;
+  const CellEnterRegion({required this.child, required this.accessories, Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Selector<CellContainerNotifier, bool>(
+      selector: (context, notifier) => notifier.onEnter,
+      builder: (context, onEnter, _) {
+        List<Widget> children = [child];
+        if (onEnter) {
+          children.add(CellAccessoryContainer(accessories: accessories).positioned(right: 0));
+        }
+
+        return MouseRegion(
+          cursor: SystemMouseCursors.click,
+          onEnter: (p) => Provider.of<CellContainerNotifier>(context, listen: false).onEnter = true,
+          onExit: (p) => Provider.of<CellContainerNotifier>(context, listen: false).onEnter = false,
+          child: Stack(
+            alignment: AlignmentDirectional.center,
+            fit: StackFit.expand,
+            children: children,
+          ),
+        );
+      },
+    );
+  }
+}
+
+class CellContainerNotifier extends ChangeNotifier {
+  final CellEditable cellEditable;
+  bool mouted = false;
+  VoidCallback? _onCellFocusListener;
+  bool _isFocus = false;
+  bool _onEnter = false;
+
+  CellContainerNotifier(this.cellEditable) {
+    _onCellFocusListener = () => isFocus = cellEditable.onCellFocus.value;
+    cellEditable.onCellFocus.addListener(_onCellFocusListener!);
+  }
+
+  @override
+  void dispose() {
+    if (_onCellFocusListener != null) {
+      cellEditable.onCellFocus.removeListener(_onCellFocusListener!);
+    }
+    super.dispose();
+  }
+
+  set isFocus(bool value) {
+    if (_isFocus != value) {
+      _isFocus = value;
+      notifyListeners();
+    }
+  }
+
+  set onEnter(bool value) {
+    if (_onEnter != value) {
+      _onEnter = value;
+      notifyListeners();
+    }
+  }
+
+  bool get isFocus => _isFocus;
+
+  bool get onEnter => _onEnter;
+}

+ 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 GridCellState<DateCell> {
 
   void _showCalendar(BuildContext context) {
     final bloc = context.read<DateCellBloc>();
-    widget.isFocus.value = true;
-    final calendar = DateCellEditor(onDismissed: () => widget.isFocus.value = false);
+    widget.onCellEditing.value = true;
+    final calendar = DateCellEditor(onDismissed: () => widget.onCellEditing.value = false);
     calendar.show(
       context,
       cellContext: bloc.cellContext.clone(),

+ 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.isFocus.value = value,
+              onFocus: (value) => widget.onCellEditing.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.isFocus.value = value,
+              onFocus: (value) => widget.onCellEditing.value = value,
               cellContextBuilder: widget.cellContextBuilder);
         },
       ),

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

@@ -112,7 +112,6 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
               child: GestureDetector(
             child: Align(alignment: Alignment.centerLeft, child: richText),
             onTap: () async {
-              widget.isFocus.value = true;
               final url = context.read<URLCellBloc>().state.url;
               await _openUrlOrEdit(url);
             },
@@ -131,12 +130,12 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
   Future<void> _openUrlOrEdit(String url) async {
     final uri = Uri.parse(url);
     if (url.isNotEmpty && await canLaunchUrl(uri)) {
-      widget.isFocus.value = false;
       await launchUrl(uri);
     } else {
       final cellContext = widget.cellContextBuilder.build() as GridURLCellContext;
+      widget.onCellEditing.value = true;
       URLCellEditor.show(context, cellContext, () {
-        widget.isFocus.value = false;
+        widget.onCellEditing.value = false;
       });
     }
   }

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

@@ -1,6 +1,7 @@
 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/cell_cotainer.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';
@@ -172,16 +173,15 @@ class _RowCells extends StatelessWidget {
     return gridCellMap.values.map(
       (gridCell) {
         final GridCellWidget child = buildGridCellWidget(gridCell, cellCache);
-        List<GridCellAccessory> accessories = [];
-        if (gridCell.field.isPrimary) {
-          accessories.add(_PrimaryCellAccessory(onTapCallback: onExpand));
-        }
 
-        accessoryBuilder(buildContext) {
+        accessoryBuilder(GridCellAccessoryBuildContext buildContext) {
           final builder = child.accessoryBuilder;
           List<GridCellAccessory> accessories = [];
           if (gridCell.field.isPrimary) {
-            accessories.add(_PrimaryCellAccessory(onTapCallback: onExpand));
+            accessories.add(PrimaryCellAccessory(
+              onTapCallback: onExpand,
+              isCellEditing: buildContext.isCellEditing,
+            ));
           }
 
           if (builder != null) {
@@ -214,22 +214,6 @@ class RegionStateNotifier extends ChangeNotifier {
   bool get onEnter => _onEnter;
 }
 
-class _PrimaryCellAccessory extends StatelessWidget with GridCellAccessory {
-  final VoidCallback onTapCallback;
-  const _PrimaryCellAccessory({required this.onTapCallback, Key? key}) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    final theme = context.watch<AppTheme>();
-    return svgWidget("grid/expander", color: theme.main1);
-  }
-
-  @override
-  void onTap() {
-    onTapCallback();
-  }
-}
-
 class _RowEnterRegion extends StatefulWidget {
   final Widget child;
   const _RowEnterRegion({required this.child, Key? key}) : super(key: key);