diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_accessory.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_accessory.dart index bfd0a38909..88eadb39c4 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_accessory.dart +++ b/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(); + return svgWidget("grid/expander", color: theme.main1); + } + } + + @override + void onTap() => onTapCallback(); + + @override + bool enable() => !isCellEditing; } typedef AccessoryBuilder = List Function(GridCellAccessoryBuildContext buildContext); @@ -21,8 +55,8 @@ typedef AccessoryBuilder = List 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? get isFocus; + // The hover will show if the isHover's value is true + ValueNotifier? get onAccessoryHover; AccessoryBuilder? get accessoryBuilder; } @@ -47,8 +81,8 @@ class _AccessoryHoverState extends State { @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 { _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 { 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 { 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(); return Consumer( 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 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(); - 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( diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart index 8bfab28f65..7147272038 100755 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart +++ b/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 get onCellFocus; + + ValueNotifier 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 isFocus = ValueNotifier(false); + final ValueNotifier onCellFocus = ValueNotifier(false); + + // When the cell is focused, we assume that the accessory alse be hovered. + @override + ValueNotifier get onAccessoryHover => onCellFocus; + + @override + final ValueNotifier onCellEditing = ValueNotifier(false); @override List Function(GridCellAccessoryBuildContext buildContext)? get accessoryBuilder => null; @@ -137,9 +151,9 @@ abstract class GridFocusNodeCellState 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( - create: (_) => CellStateNotifier(), - update: (_, row, cell) => cell!..onEnter = row.onEnter, - child: Selector( - selector: (context, notifier) => notifier.isFocus, - builder: (context, isFocus, _) { - Widget container = Center(child: GridCellShortcuts(child: child)); - child.isFocus.addListener(() { - Provider.of(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(); - 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 accessories; - const CellEnterRegion({required this.child, required this.accessories, Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Selector( - selector: (context, notifier) => notifier.onEnter, - builder: (context, onEnter, _) { - List children = [child]; - if (onEnter) { - children.add(AccessoryContainer(accessories: accessories).positioned(right: 0)); - } - - return MouseRegion( - cursor: SystemMouseCursors.click, - onEnter: (p) => Provider.of(context, listen: false).onEnter = true, - onExit: (p) => Provider.of(context, listen: false).onEnter = false, - child: Stack( - alignment: AlignmentDirectional.center, - fit: StackFit.expand, - children: children, - ), - ); - }, - ); - } -} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_cotainer.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_cotainer.dart new file mode 100644 index 0000000000..fbb343dd6c --- /dev/null +++ b/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( + create: (_) => CellContainerNotifier(child), + update: (_, rowStateNotifier, cellStateNotifier) => cellStateNotifier!..onEnter = rowStateNotifier.onEnter, + child: Selector( + 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(); + 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 accessories; + const CellEnterRegion({required this.child, required this.accessories, Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Selector( + selector: (context, notifier) => notifier.onEnter, + builder: (context, onEnter, _) { + List children = [child]; + if (onEnter) { + children.add(CellAccessoryContainer(accessories: accessories).positioned(right: 0)); + } + + return MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: (p) => Provider.of(context, listen: false).onEnter = true, + onExit: (p) => Provider.of(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; +} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_cell.dart index eb382a0971..3e7d40c796 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_cell.dart @@ -76,8 +76,8 @@ class _DateCellState extends GridCellState { void _showCalendar(BuildContext context) { final bloc = context.read(); - 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(), diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_cell.dart index 294a87976f..e878ac2c58 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_cell.dart +++ b/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 { 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 { return _SelectOptionCell( selectOptions: state.selectedOptions, cellStyle: widget.cellStyle, - onFocus: (value) => widget.isFocus.value = value, + onFocus: (value) => widget.onCellEditing.value = value, cellContextBuilder: widget.cellContextBuilder); }, ), diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/url_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/url_cell.dart index b3e9c3c919..e37dca6632 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/url_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/url_cell.dart @@ -112,7 +112,6 @@ class _GridURLCellState extends GridCellState { child: GestureDetector( child: Align(alignment: Alignment.centerLeft, child: richText), onTap: () async { - widget.isFocus.value = true; final url = context.read().state.url; await _openUrlOrEdit(url); }, @@ -131,12 +130,12 @@ class _GridURLCellState extends GridCellState { Future _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; }); } } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart index b646af6c40..15dbb0bfc7 100755 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart +++ b/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 accessories = []; - if (gridCell.field.isPrimary) { - accessories.add(_PrimaryCellAccessory(onTapCallback: onExpand)); - } - accessoryBuilder(buildContext) { + accessoryBuilder(GridCellAccessoryBuildContext buildContext) { final builder = child.accessoryBuilder; List 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(); - 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);