From 40443ced8081a48c6f2db7297a176e3227a81f84 Mon Sep 17 00:00:00 2001 From: appflowy Date: Fri, 29 Apr 2022 10:46:35 +0800 Subject: [PATCH 1/5] feat: expand row --- .../grid/src/widgets/cell/checkbox_cell.dart | 17 ++++++----- .../cell/selection_cell/selection_cell.dart | 3 +- .../grid/src/widgets/cell/text_cell.dart | 27 +++++++++-------- .../grid/src/widgets/row/grid_row.dart | 30 ++++++++----------- 4 files changed, 40 insertions(+), 37 deletions(-) diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart index 99fcd4a26fd56..7a69db02f2567 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart @@ -35,13 +35,16 @@ class _CheckboxCellState extends State { child: BlocBuilder( builder: (context, state) { final icon = state.isSelected ? svgWidget('editor/editor_check') : svgWidget('editor/editor_uncheck'); - return Align( - alignment: Alignment.centerLeft, - child: FlowyIconButton( - onPressed: () => context.read().add(const CheckboxCellEvent.select()), - iconPadding: EdgeInsets.zero, - icon: icon, - width: 23, + return SizedBox( + height: 42, + child: Align( + alignment: Alignment.centerLeft, + child: FlowyIconButton( + onPressed: () => context.read().add(const CheckboxCellEvent.select()), + iconPadding: EdgeInsets.zero, + icon: icon, + width: 23, + ), ), ); }, diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart index fe10341dcfb1a..5edb3148d1381 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart @@ -64,7 +64,8 @@ class _SingleSelectCellState extends State { if (children.isEmpty && widget.cellStyle != null) { children.add(FlowyText.medium(widget.cellStyle!.placeholder, fontSize: 14, color: theme.shader3)); } - return SizedBox.expand( + return SizedBox( + height: 69, child: InkWell( onTap: () { widget.onFocus.value = true; diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart index 822dc69fb94d4..a7b548fda542d 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart @@ -68,18 +68,21 @@ class _GridTextCellState extends State { }, buildWhen: (previous, current) => previous.content != current.content, builder: (context, state) { - return TextField( - controller: _controller, - focusNode: _focusNode, - onChanged: (value) => focusChanged(), - onEditingComplete: () => _focusNode.unfocus(), - maxLines: 1, - style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), - decoration: InputDecoration( - contentPadding: EdgeInsets.zero, - border: InputBorder.none, - hintText: widget.cellStyle?.placeholder, - isDense: true, + return SizedBox( + height: 42, + child: TextField( + controller: _controller, + focusNode: _focusNode, + onChanged: (value) => focusChanged(), + onEditingComplete: () => _focusNode.unfocus(), + maxLines: 1, + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), + decoration: InputDecoration( + contentPadding: EdgeInsets.zero, + border: InputBorder.none, + hintText: widget.cellStyle?.placeholder, + isDense: true, + ), ), ); }, 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 eb744af7b20d3..195899c2963f5 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 @@ -48,19 +48,13 @@ class _GridRowWidgetState extends State { child: BlocBuilder( buildWhen: (p, c) => p.rowData.height != c.rowData.height, builder: (context, state) { - final children = [ - const _RowLeading(), - _RowCells(cellCache: widget.cellCache, onExpand: () => onExpandCell(context)), - const _RowTrailing(), - ]; - - final child = Row( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.center, - children: children, + return Row( + children: [ + const _RowLeading(), + Expanded(child: _RowCells(cellCache: widget.cellCache, onExpand: () => _expandRow(context))), + const _RowTrailing(), + ], ); - - return SizedBox(height: 42, child: child); }, ), ), @@ -73,7 +67,7 @@ class _GridRowWidgetState extends State { super.dispose(); } - void onExpandCell(BuildContext context) { + void _expandRow(BuildContext context) { final page = RowDetailPage( rowData: widget.rowData, rowCache: widget.rowCache, @@ -161,11 +155,13 @@ class _RowCells extends StatelessWidget { return BlocBuilder( buildWhen: (previous, current) => previous.cellDataMap.length != current.cellDataMap.length, builder: (context, state) { - return Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, + return IntrinsicHeight( + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, children: _makeCells(context, state.cellDataMap), - ); + )); }, ); } From 1ad7e0ece2d4c9acf60e09eea6c281e88867754b Mon Sep 17 00:00:00 2001 From: appflowy Date: Fri, 29 Apr 2022 22:03:53 +0800 Subject: [PATCH 2/5] feat: using wrap to auto expand Multi-select or single select --- .../plugins/grid/src/layout/sizes.dart | 4 +- .../grid/src/widgets/cell/cell_builder.dart | 117 ++++++++++++++++++ .../grid/src/widgets/cell/cell_container.dart | 115 ----------------- .../grid/src/widgets/cell/checkbox_cell.dart | 2 +- .../grid/src/widgets/cell/number_cell.dart | 2 +- .../grid/src/widgets/cell/prelude.dart | 1 - .../cell/selection_cell/extension.dart | 28 +++-- .../cell/selection_cell/selection_cell.dart | 9 +- .../cell/selection_cell/text_field.dart | 2 +- .../grid/src/widgets/cell/text_cell.dart | 27 ++-- .../grid/src/widgets/row/grid_row.dart | 6 +- 11 files changed, 160 insertions(+), 153 deletions(-) delete mode 100755 frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_container.dart diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/sizes.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/sizes.dart index 8dc0b202639cf..0e93dc6a21636 100755 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/sizes.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/sizes.dart @@ -9,8 +9,8 @@ class GridSize { static double get leadingHeaderPadding => 50 * scale; static double get trailHeaderPadding => 140 * scale; static double get headerContainerPadding => 0 * scale; - static double get cellHPadding => 10 * scale; - static double get cellVPadding => 8 * scale; + static double get cellHPadding => 12 * scale; + static double get cellVPadding => 12 * scale; static double get typeOptionItemHeight => 32 * scale; static double get typeOptionSeparatorHeight => 6 * scale; 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 4d62dc9eb213d..b3af02a29dddb 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,6 +2,12 @@ import 'package:app_flowy/workspace/application/grid/cell/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'; +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 'checkbox_cell.dart'; import 'date_cell.dart'; import 'number_cell.dart'; @@ -57,3 +63,114 @@ class GridCellRequestFocusNotifier extends ChangeNotifier { } abstract class GridCellStyle {} + +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; +} + +class CellContainer extends StatelessWidget { + final GridCellWidget child; + final Widget? expander; + final double width; + final RegionStateNotifier rowStateNotifier; + const CellContainer({ + Key? key, + required this.child, + required this.width, + required this.rowStateNotifier, + this.expander, + }) : 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: child); + child.onFocus.addListener(() { + Provider.of(context, listen: false).isFocus = child.onFocus.value; + }); + + if (expander != null) { + container = _CellEnterRegion(child: container, expander: expander!); + } + + return GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () => child.requestFocus.notify(), + child: Container( + constraints: BoxConstraints(maxWidth: width), + 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 Widget expander; + const _CellEnterRegion({required this.child, required this.expander, 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(expander.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, + // alignment: AlignmentDirectional.centerEnd, + children: children, + ), + ); + }, + ); + } +} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_container.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_container.dart deleted file mode 100755 index 89f3700a0d230..0000000000000 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_container.dart +++ /dev/null @@ -1,115 +0,0 @@ -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 'cell_builder.dart'; - -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; -} - -class CellContainer extends StatelessWidget { - final GridCellWidget child; - final Widget? expander; - final double width; - final RegionStateNotifier rowStateNotifier; - const CellContainer({ - Key? key, - required this.child, - required this.width, - required this.rowStateNotifier, - this.expander, - }) : 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: child); - child.onFocus.addListener(() { - Provider.of(context, listen: false).isFocus = child.onFocus.value; - }); - - if (expander != null) { - container = _CellEnterRegion(child: container, expander: expander!); - } - - return GestureDetector( - behavior: HitTestBehavior.translucent, - onTap: () => child.requestFocus.notify(), - child: Container( - constraints: BoxConstraints(maxWidth: width), - 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.shader4, width: 0.4); - return BoxDecoration(border: Border(right: borderSide, bottom: borderSide)); - } - } -} - -class _CellEnterRegion extends StatelessWidget { - final Widget child; - final Widget expander; - const _CellEnterRegion({required this.child, required this.expander, Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Selector( - selector: (context, notifier) => notifier.onEnter, - builder: (context, onEnter, _) { - List children = [Expanded(child: child)]; - if (onEnter) { - children.add(expander); - } - - return MouseRegion( - cursor: SystemMouseCursors.click, - onEnter: (p) => Provider.of(context, listen: false).onEnter = true, - onExit: (p) => Provider.of(context, listen: false).onEnter = false, - child: Row( - // alignment: AlignmentDirectional.centerEnd, - children: children, - ), - ); - }, - ); - } -} diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart index 7a69db02f2567..97754e339eb72 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart @@ -36,7 +36,7 @@ class _CheckboxCellState extends State { builder: (context, state) { final icon = state.isSelected ? svgWidget('editor/editor_check') : svgWidget('editor/editor_uncheck'); return SizedBox( - height: 42, + height: 20, child: Align( alignment: Alignment.centerLeft, child: FlowyIconButton( diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart index 9901790fa5c84..b45f964bdc952 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart @@ -55,7 +55,7 @@ class _NumberCellState extends State { controller: _controller, focusNode: _focusNode, onEditingComplete: () => _focusNode.unfocus(), - maxLines: 1, + maxLines: null, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), decoration: const InputDecoration( contentPadding: EdgeInsets.zero, diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/prelude.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/prelude.dart index 7fc3b3246fbd3..8f1e13de94dc3 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/prelude.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/prelude.dart @@ -1,5 +1,4 @@ export 'cell_builder.dart'; -export 'cell_container.dart'; export 'text_cell.dart'; export 'number_cell.dart'; export 'date_cell.dart'; diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/extension.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/extension.dart index bdf4d01158ba2..71a86e91b422c 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/extension.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/extension.dart @@ -66,15 +66,25 @@ class SelectOptionTag extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - color: option.color.make(context), - shape: BoxShape.rectangle, - borderRadius: BorderRadius.circular(8.0), - ), - child: Center(child: FlowyText.medium(option.name, fontSize: 12)), - margin: const EdgeInsets.symmetric(horizontal: 3.0), - padding: const EdgeInsets.symmetric(horizontal: 6.0), + return ChoiceChip( + pressElevation: 1, + label: FlowyText.medium(option.name, fontSize: 12), + selectedColor: option.color.make(context), + backgroundColor: option.color.make(context), + labelPadding: const EdgeInsets.symmetric(horizontal: 6), + selected: true, + onSelected: (_) {}, ); + + // return Container( + // decoration: BoxDecoration( + // color: option.color.make(context), + // shape: BoxShape.rectangle, + // borderRadius: BorderRadius.circular(8.0), + // ), + // child: Center(child: FlowyText.medium(option.name, fontSize: 12)), + // margin: const EdgeInsets.symmetric(horizontal: 3.0), + // padding: const EdgeInsets.symmetric(horizontal: 6.0), + // ); } } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart index 5edb3148d1381..e23af426e6255 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart @@ -44,7 +44,6 @@ class _SingleSelectCellState extends State { @override void initState() { - // Log.trace("init widget $hashCode"); final cellContext = _buildCellContext(); _cellBloc = getIt(param1: cellContext)..add(const SelectionCellEvent.initial()); super.initState(); @@ -64,8 +63,7 @@ class _SingleSelectCellState extends State { if (children.isEmpty && widget.cellStyle != null) { children.add(FlowyText.medium(widget.cellStyle!.placeholder, fontSize: 14, color: theme.shader3)); } - return SizedBox( - height: 69, + return SizedBox.expand( child: InkWell( onTap: () { widget.onFocus.value = true; @@ -75,7 +73,7 @@ class _SingleSelectCellState extends State { () => widget.onFocus.value = false, ); }, - child: ClipRRect(child: Row(children: children)), + child: Center(child: Wrap(children: children)), ), ); }, @@ -89,7 +87,6 @@ class _SingleSelectCellState extends State { @override Future dispose() async { - // Log.trace("dispose widget $hashCode"); _cellBloc.close(); super.dispose(); } @@ -148,7 +145,7 @@ class _MultiSelectCellState extends State { () => widget.onFocus.value = false, ); }, - child: ClipRRect(child: Row(children: children)), + child: Wrap(children: children, spacing: 4, runSpacing: 4), ), ); }, diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/text_field.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/text_field.dart index e7f4e98851ff0..0bc33fc8d246d 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/text_field.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/text_field.dart @@ -94,7 +94,7 @@ class SelectOptionTextField extends StatelessWidget { child: SingleChildScrollView( controller: sc, scrollDirection: Axis.horizontal, - child: Row(children: children), + child: Wrap(children: children, spacing: 4), ), ); } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart index a7b548fda542d..2b3a72525f99b 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart @@ -68,21 +68,18 @@ class _GridTextCellState extends State { }, buildWhen: (previous, current) => previous.content != current.content, builder: (context, state) { - return SizedBox( - height: 42, - child: TextField( - controller: _controller, - focusNode: _focusNode, - onChanged: (value) => focusChanged(), - onEditingComplete: () => _focusNode.unfocus(), - maxLines: 1, - style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), - decoration: InputDecoration( - contentPadding: EdgeInsets.zero, - border: InputBorder.none, - hintText: widget.cellStyle?.placeholder, - isDense: true, - ), + return TextField( + controller: _controller, + focusNode: _focusNode, + onChanged: (value) => focusChanged(), + onEditingComplete: () => _focusNode.unfocus(), + maxLines: null, + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), + decoration: InputDecoration( + contentPadding: EdgeInsets.zero, + border: InputBorder.none, + hintText: widget.cellStyle?.placeholder, + isDense: true, ), ); }, 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 195899c2963f5..d4eaa21a90d12 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 @@ -206,9 +206,11 @@ class _CellExpander extends StatelessWidget { Widget build(BuildContext context) { final theme = context.watch(); return FlowyIconButton( - width: 20, + width: 30, + height: 24, onPressed: onExpand, - iconPadding: const EdgeInsets.fromLTRB(2, 2, 2, 2), + iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4), + fillColor: theme.surface, icon: svgWidget("grid/expander", color: theme.main1), ); } From 371f75343ccccdaed6e548ff0b821daaae0b1f76 Mon Sep 17 00:00:00 2001 From: appflowy Date: Sat, 30 Apr 2022 08:21:27 +0800 Subject: [PATCH 3/5] chore: add min height for row --- .../plugins/grid/src/layout/sizes.dart | 4 +- .../grid/src/widgets/cell/cell_builder.dart | 2 +- .../grid/src/widgets/cell/checkbox_cell.dart | 17 ++- .../cell/selection_cell/selection_cell.dart | 106 ++++++++++-------- .../cell/selection_cell/selection_editor.dart | 71 +++++++----- .../grid/src/widgets/row/grid_row.dart | 15 +-- 6 files changed, 116 insertions(+), 99 deletions(-) diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/sizes.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/sizes.dart index 0e93dc6a21636..62577ee62c191 100755 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/sizes.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/sizes.dart @@ -9,8 +9,8 @@ class GridSize { static double get leadingHeaderPadding => 50 * scale; static double get trailHeaderPadding => 140 * scale; static double get headerContainerPadding => 0 * scale; - static double get cellHPadding => 12 * scale; - static double get cellVPadding => 12 * scale; + static double get cellHPadding => 8 * scale; + static double get cellVPadding => 8 * scale; static double get typeOptionItemHeight => 32 * scale; static double get typeOptionSeparatorHeight => 6 * scale; 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 b3af02a29dddb..2049e9583bc93 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 @@ -121,7 +121,7 @@ class CellContainer extends StatelessWidget { behavior: HitTestBehavior.translucent, onTap: () => child.requestFocus.notify(), child: Container( - constraints: BoxConstraints(maxWidth: width), + constraints: BoxConstraints(maxWidth: width, minHeight: 46), decoration: _makeBoxDecoration(context, isFocus), padding: GridSize.cellContentInsets, child: container, diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart index 97754e339eb72..99fcd4a26fd56 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart @@ -35,16 +35,13 @@ class _CheckboxCellState extends State { child: BlocBuilder( builder: (context, state) { final icon = state.isSelected ? svgWidget('editor/editor_check') : svgWidget('editor/editor_uncheck'); - return SizedBox( - height: 20, - child: Align( - alignment: Alignment.centerLeft, - child: FlowyIconButton( - onPressed: () => context.read().add(const CheckboxCellEvent.select()), - iconPadding: EdgeInsets.zero, - icon: icon, - width: 23, - ), + return Align( + alignment: Alignment.centerLeft, + child: FlowyIconButton( + onPressed: () => context.read().add(const CheckboxCellEvent.select()), + iconPadding: EdgeInsets.zero, + icon: icon, + width: 23, ), ); }, diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart index e23af426e6255..01aad63699d3d 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart @@ -5,6 +5,7 @@ import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; // ignore: unused_import import 'package:flowy_sdk/log.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -44,47 +45,27 @@ class _SingleSelectCellState extends State { @override void initState() { - final cellContext = _buildCellContext(); + final cellContext = widget.cellContextBuilder.build() as GridSelectOptionCellContext; _cellBloc = getIt(param1: cellContext)..add(const SelectionCellEvent.initial()); super.initState(); } @override Widget build(BuildContext context) { - final theme = context.watch(); - // Log.trace("build widget $hashCode"); return BlocProvider.value( value: _cellBloc, child: BlocBuilder( builder: (context, state) { - List children = []; - children.addAll(state.selectedOptions.map((option) => SelectOptionTag(option: option)).toList()); - - if (children.isEmpty && widget.cellStyle != null) { - children.add(FlowyText.medium(widget.cellStyle!.placeholder, fontSize: 14, color: theme.shader3)); - } - return SizedBox.expand( - child: InkWell( - onTap: () { - widget.onFocus.value = true; - SelectOptionCellEditor.show( - context, - _buildCellContext(), - () => widget.onFocus.value = false, - ); - }, - child: Center(child: Wrap(children: children)), - ), - ); + return _SelectOptionCell( + selectOptions: state.selectedOptions, + cellStyle: widget.cellStyle, + onFocus: (value) => widget.onFocus.value = value, + cellContextBuilder: widget.cellContextBuilder); }, ), ); } - GridSelectOptionCellContext _buildCellContext() { - return widget.cellContextBuilder.build() as GridSelectOptionCellContext; - } - @override Future dispose() async { _cellBloc.close(); @@ -118,7 +99,7 @@ class _MultiSelectCellState extends State { @override void initState() { - final cellContext = _buildCellContext(); + final cellContext = widget.cellContextBuilder.build() as GridSelectOptionCellContext; _cellBloc = getIt(param1: cellContext)..add(const SelectionCellEvent.initial()); super.initState(); } @@ -129,25 +110,11 @@ class _MultiSelectCellState extends State { value: _cellBloc, child: BlocBuilder( builder: (context, state) { - List children = state.selectedOptions.map((option) => SelectOptionTag(option: option)).toList(); - - if (children.isEmpty && widget.cellStyle != null) { - children.add(FlowyText.medium(widget.cellStyle!.placeholder, fontSize: 14)); - } - - return SizedBox.expand( - child: InkWell( - onTap: () { - widget.onFocus.value = true; - SelectOptionCellEditor.show( - context, - _buildCellContext(), - () => widget.onFocus.value = false, - ); - }, - child: Wrap(children: children, spacing: 4, runSpacing: 4), - ), - ); + return _SelectOptionCell( + selectOptions: state.selectedOptions, + cellStyle: widget.cellStyle, + onFocus: (value) => widget.onFocus.value = value, + cellContextBuilder: widget.cellContextBuilder); }, ), ); @@ -158,8 +125,51 @@ class _MultiSelectCellState extends State { _cellBloc.close(); super.dispose(); } +} + +class _SelectOptionCell extends StatelessWidget { + final List selectOptions; + final void Function(bool) onFocus; + final SelectOptionCellStyle? cellStyle; + final GridCellContextBuilder cellContextBuilder; + const _SelectOptionCell({ + required this.selectOptions, + required this.onFocus, + required this.cellStyle, + required this.cellContextBuilder, + Key? key, + }) : super(key: key); - GridSelectOptionCellContext _buildCellContext() { - return widget.cellContextBuilder.build() as GridSelectOptionCellContext; + @override + Widget build(BuildContext context) { + final theme = context.watch(); + final Widget child; + if (selectOptions.isEmpty && cellStyle != null) { + child = Align( + alignment: Alignment.centerLeft, + child: FlowyText.medium(cellStyle!.placeholder, fontSize: 14, color: theme.shader3), + ); + } else { + final tags = selectOptions.map((option) => SelectOptionTag(option: option)).toList(); + child = Align( + alignment: Alignment.centerLeft, + child: Wrap(children: tags, spacing: 4, runSpacing: 4), + ); + } + + final cellContext = cellContextBuilder.build() as GridSelectOptionCellContext; + return Stack( + alignment: AlignmentDirectional.center, + fit: StackFit.expand, + children: [ + child, + InkWell( + onTap: () { + onFocus(true); + SelectOptionCellEditor.show(context, cellContext, () => onFocus(false)); + }, + ), + ], + ); } } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_editor.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_editor.dart index 0379bd55dd753..4152e7b68f1dc 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_editor.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_editor.dart @@ -184,41 +184,50 @@ class _SelectOptionCell extends StatelessWidget { final theme = context.watch(); return SizedBox( height: GridSize.typeOptionItemHeight, - child: InkWell( - onTap: () { - context.read().add(SelectOptionEditorEvent.selectOption(option.id)); - }, - child: FlowyHover( - style: HoverStyle(hoverColor: theme.hover), - builder: (_, onHover) { - List children = [ - SelectOptionTag(option: option, isSelected: isSelected), - const Spacer(), - ]; - - if (isSelected) { - children.add(svgWidget("grid/checkmark")); - } - - if (onHover) { - children.add(FlowyIconButton( - width: 30, - onPressed: () => _showEditPannel(context), - iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4), - icon: svgWidget("editor/details", color: theme.iconColor), - )); - } - - return Padding( - padding: const EdgeInsets.all(3.0), - child: Row(children: children), - ); - }, - ), + child: Stack( + fit: StackFit.expand, + children: [ + _body(theme, context), + InkWell( + onTap: () { + context.read().add(SelectOptionEditorEvent.selectOption(option.id)); + }, + ), + ], ), ); } + FlowyHover _body(AppTheme theme, BuildContext context) { + return FlowyHover( + style: HoverStyle(hoverColor: theme.hover), + builder: (_, onHover) { + List children = [ + SelectOptionTag(option: option, isSelected: isSelected), + const Spacer(), + ]; + + if (isSelected) { + children.add(svgWidget("grid/checkmark")); + } + + if (onHover) { + children.add(FlowyIconButton( + width: 30, + onPressed: () => _showEditPannel(context), + iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4), + icon: svgWidget("editor/details", color: theme.iconColor), + )); + } + + return Padding( + padding: const EdgeInsets.all(3.0), + child: Row(children: children), + ); + }, + ); + } + void _showEditPannel(BuildContext context) { final pannel = EditSelectOptionPannel( option: option, 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 d4eaa21a90d12..8d8d66fa03c44 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 @@ -205,13 +205,14 @@ class _CellExpander extends StatelessWidget { @override Widget build(BuildContext context) { final theme = context.watch(); - return FlowyIconButton( - width: 30, - height: 24, - onPressed: onExpand, - iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4), - fillColor: theme.surface, - icon: svgWidget("grid/expander", color: theme.main1), + return FittedBox( + fit: BoxFit.contain, + child: FlowyIconButton( + onPressed: onExpand, + iconPadding: const EdgeInsets.fromLTRB(6, 6, 6, 6), + fillColor: theme.surface, + icon: svgWidget("grid/expander", color: theme.main1), + ), ); } } From 6b5126b12ef6a4fdfade25a58298f95d058fec32 Mon Sep 17 00:00:00 2001 From: appflowy Date: Sat, 30 Apr 2022 09:29:43 +0800 Subject: [PATCH 4/5] refactor: GridCellRequestFocusNotifier only accepts single listener --- .../grid/src/widgets/cell/cell_builder.dart | 18 ++++++++++ .../grid/src/widgets/cell/checkbox_cell.dart | 1 + .../grid/src/widgets/cell/number_cell.dart | 16 ++------- .../cell/selection_cell/selection_cell.dart | 2 +- .../grid/src/widgets/cell/text_cell.dart | 36 +++++++++---------- .../grid/src/widgets/row/grid_row.dart | 2 +- 6 files changed, 41 insertions(+), 34 deletions(-) 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 2049e9583bc93..8d7ea06c545c8 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 @@ -57,6 +57,24 @@ abstract class GridCellWidget extends HoverWidget { } class GridCellRequestFocusNotifier extends ChangeNotifier { + VoidCallback? _listener; + + @override + void addListener(VoidCallback listener) { + if (_listener != null) { + removeListener(_listener!); + } + + _listener = listener; + super.addListener(listener); + } + + void removeAllListener() { + if (_listener != null) { + removeListener(_listener!); + } + } + void notify() { notifyListeners(); } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart index 99fcd4a26fd56..c4da2a223f72d 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart @@ -57,6 +57,7 @@ class _CheckboxCellState extends State { @override Future dispose() async { + widget.requestFocus.removeAllListener(); _cellBloc.close(); super.dispose(); } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart index b45f964bdc952..c012db3304c88 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart @@ -23,7 +23,6 @@ class _NumberCellState extends State { late NumberCellBloc _cellBloc; late TextEditingController _controller; late FocusNode _focusNode; - VoidCallback? _focusListener; Timer? _delayOperation; @override @@ -70,9 +69,7 @@ class _NumberCellState extends State { @override Future dispose() async { - if (_focusListener != null) { - widget.requestFocus.removeListener(_focusListener!); - } + widget.requestFocus.removeAllListener(); _delayOperation?.cancel(); _cellBloc.close(); _focusNode.dispose(); @@ -96,17 +93,10 @@ class _NumberCellState extends State { } void _listenCellRequestFocus(BuildContext context) { - if (_focusListener != null) { - widget.requestFocus.removeListener(_focusListener!); - } - - focusListener() { + widget.requestFocus.addListener(() { if (_focusNode.hasFocus == false && _focusNode.canRequestFocus) { FocusScope.of(context).requestFocus(_focusNode); } - } - - _focusListener = focusListener; - widget.requestFocus.addListener(focusListener); + }); } } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart index 01aad63699d3d..73aa583956ca7 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart @@ -157,7 +157,6 @@ class _SelectOptionCell extends StatelessWidget { ); } - final cellContext = cellContextBuilder.build() as GridSelectOptionCellContext; return Stack( alignment: AlignmentDirectional.center, fit: StackFit.expand, @@ -166,6 +165,7 @@ class _SelectOptionCell extends StatelessWidget { InkWell( onTap: () { onFocus(true); + final cellContext = cellContextBuilder.build() as GridSelectOptionCellContext; SelectOptionCellEditor.show(context, cellContext, () => onFocus(false)); }, ), diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart index 2b3a72525f99b..affcacafd2ee8 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart @@ -36,7 +36,8 @@ class _GridTextCellState extends State { late TextCellBloc _cellBloc; late TextEditingController _controller; late FocusNode _focusNode; - VoidCallback? _focusListener; + + VoidCallback? _focusNodeListener; Timer? _delayOperation; @override @@ -87,32 +88,29 @@ class _GridTextCellState extends State { ); } - void _listenCellRequestFocus(BuildContext context) { - if (_focusListener != null) { - widget.requestFocus.removeListener(_focusListener!); - } - - focusListener() { - if (_focusNode.hasFocus == false && _focusNode.canRequestFocus) { - FocusScope.of(context).requestFocus(_focusNode); - } - } - - _focusListener = focusListener; - widget.requestFocus.addListener(focusListener); - } - @override Future dispose() async { - if (_focusListener != null) { - widget.requestFocus.removeListener(_focusListener!); - } + widget.requestFocus.removeAllListener(); _delayOperation?.cancel(); _cellBloc.close(); _focusNode.dispose(); super.dispose(); } + @override + void didUpdateWidget(covariant GridTextCell oldWidget) { + // TODO: implement didUpdateWidget + super.didUpdateWidget(oldWidget); + } + + void _listenCellRequestFocus(BuildContext context) { + widget.requestFocus.addListener(() { + if (_focusNode.hasFocus == false && _focusNode.canRequestFocus) { + FocusScope.of(context).requestFocus(_focusNode); + } + }); + } + Future focusChanged() async { if (mounted) { _delayOperation?.cancel(); 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 8d8d66fa03c44..68908f1243d38 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 @@ -153,7 +153,7 @@ class _RowCells extends StatelessWidget { @override Widget build(BuildContext context) { return BlocBuilder( - buildWhen: (previous, current) => previous.cellDataMap.length != current.cellDataMap.length, + buildWhen: (previous, current) => previous.cellDataMap != current.cellDataMap, builder: (context, state) { return IntrinsicHeight( child: Row( From 518d11162b55e77b6dbeb6ed5d6dfba9415634c4 Mon Sep 17 00:00:00 2001 From: appflowy Date: Sat, 30 Apr 2022 21:19:59 +0800 Subject: [PATCH 5/5] chore: reload row when fields were changed --- .../application/grid/cell/cell_service.dart | 7 +++ .../application/grid/row/row_bloc.dart | 35 +++++++++-- .../application/grid/row/row_detail_bloc.dart | 2 +- .../application/grid/row/row_service.dart | 6 +- .../grid/src/widgets/cell/cell_builder.dart | 21 ++++++- .../grid/src/widgets/cell/number_cell.dart | 26 ++++++-- .../grid/src/widgets/cell/text_cell.dart | 63 ++++++++++--------- .../grid/src/widgets/row/grid_row.dart | 5 +- 8 files changed, 117 insertions(+), 48 deletions(-) diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service.dart index 251ca0ef2945d..5b19a48d9c8f3 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service.dart @@ -365,4 +365,11 @@ class GridCell with _$GridCell { required Field field, Cell? cell, }) = _GridCell; + + // ignore: unused_element + const GridCell._(); + + String cellId() { + return rowId + field.id + "${field.fieldType}"; + } } diff --git a/frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart index cfd99b3e98acd..3eb0ca16f4b72 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart @@ -1,5 +1,7 @@ import 'dart:collection'; import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Field; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:async'; @@ -28,7 +30,13 @@ class RowBloc extends Bloc { _rowService.createRow(); }, didReceiveCellDatas: (_DidReceiveCellDatas value) async { - emit(state.copyWith(cellDataMap: value.cellData)); + final fields = value.gridCellMap.values.map((e) => CellSnapshot(e.field)).toList(); + final snapshots = UnmodifiableListView(fields); + emit(state.copyWith( + gridCellMap: value.gridCellMap, + snapshots: snapshots, + changeReason: value.reason, + )); }, ); }, @@ -47,7 +55,7 @@ class RowBloc extends Bloc { Future _startListening() async { _rowListenFn = _rowCache.addRowListener( rowId: state.rowData.rowId, - onUpdated: (cellDatas) => add(RowEvent.didReceiveCellDatas(cellDatas)), + onUpdated: (cellDatas, reason) => add(RowEvent.didReceiveCellDatas(cellDatas, reason)), listenWhen: () => !isClosed, ); } @@ -57,18 +65,35 @@ class RowBloc extends Bloc { class RowEvent with _$RowEvent { const factory RowEvent.initial() = _InitialRow; const factory RowEvent.createRow() = _CreateRow; - const factory RowEvent.didReceiveCellDatas(GridCellMap cellData) = _DidReceiveCellDatas; + const factory RowEvent.didReceiveCellDatas(GridCellMap gridCellMap, GridRowChangeReason reason) = + _DidReceiveCellDatas; } @freezed class RowState with _$RowState { const factory RowState({ required GridRow rowData, - required GridCellMap cellDataMap, + required GridCellMap gridCellMap, + required UnmodifiableListView snapshots, + GridRowChangeReason? changeReason, }) = _RowState; factory RowState.initial(GridRow rowData, GridCellMap cellDataMap) => RowState( rowData: rowData, - cellDataMap: cellDataMap, + gridCellMap: cellDataMap, + snapshots: UnmodifiableListView(cellDataMap.values.map((e) => CellSnapshot(e.field)).toList()), ); } + +class CellSnapshot extends Equatable { + final Field _field; + + const CellSnapshot(Field field) : _field = field; + + @override + List get props => [ + _field.id, + _field.fieldType, + _field.visibility, + ]; +} diff --git a/frontend/app_flowy/lib/workspace/application/grid/row/row_detail_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/row/row_detail_bloc.dart index 4228ddd863991..2edd818b9b2da 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/row/row_detail_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/row/row_detail_bloc.dart @@ -42,7 +42,7 @@ class RowDetailBloc extends Bloc { Future _startListening() async { _rowListenFn = _rowCache.addRowListener( rowId: rowData.rowId, - onUpdated: (cellDatas) => add(RowDetailEvent.didReceiveCellDatas(cellDatas.values.toList())), + onUpdated: (cellDatas, reason) => add(RowDetailEvent.didReceiveCellDatas(cellDatas.values.toList())), listenWhen: () => !isClosed, ); } diff --git a/frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart b/frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart index 82879b647eb7f..232b8fbf0e57b 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart @@ -83,7 +83,7 @@ class GridRowCache { RowUpdateCallback addRowListener({ required String rowId, - void Function(GridCellMap)? onUpdated, + void Function(GridCellMap, GridRowChangeReason)? onUpdated, bool Function()? listenWhen, }) { listenrHandler() async { @@ -99,7 +99,7 @@ class GridRowCache { final row = _rowsNotifier.rowDataWithId(rowId); if (row != null) { final GridCellMap cellDataMap = _makeGridCells(rowId, row); - onUpdated(cellDataMap); + onUpdated(cellDataMap, _rowsNotifier._changeReason); } } @@ -339,7 +339,7 @@ class GridRow with _$GridRow { const factory GridRow({ required String gridId, required String rowId, - required List fields, + required UnmodifiableListView fields, required double height, Row? data, }) = _GridRow; 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 8d7ea06c545c8..0b13aab846226 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 @@ -15,7 +15,7 @@ import 'selection_cell/selection_cell.dart'; import 'text_cell.dart'; GridCellWidget buildGridCellWidget(GridCell gridCell, GridCellCache cellCache, {GridCellStyle? style}) { - final key = ValueKey(gridCell.rowId + gridCell.field.id); + final key = ValueKey(gridCell.cellId()); final cellContextBuilder = GridCellContextBuilder(gridCell: gridCell, cellCache: cellCache); @@ -82,6 +82,25 @@ class GridCellRequestFocusNotifier extends ChangeNotifier { abstract class GridCellStyle {} +class CellSingleFocusNode extends FocusNode { + VoidCallback? _listener; + + void setSingleListener(VoidCallback listener) { + if (_listener != null) { + removeListener(_listener!); + } + + _listener = listener; + super.addListener(listener); + } + + void removeSingleListener() { + if (_listener != null) { + removeListener(_listener!); + } + } +} + class CellStateNotifier extends ChangeNotifier { bool _isFocus = false; bool _onEnter = false; diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart index c012db3304c88..c0b3427e65260 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart @@ -22,7 +22,7 @@ class NumberCell extends GridCellWidget { class _NumberCellState extends State { late NumberCellBloc _cellBloc; late TextEditingController _controller; - late FocusNode _focusNode; + late CellSingleFocusNode _focusNode; Timer? _delayOperation; @override @@ -30,11 +30,8 @@ class _NumberCellState extends State { final cellContext = widget.cellContextBuilder.build(); _cellBloc = getIt(param1: cellContext)..add(const NumberCellEvent.initial()); _controller = TextEditingController(text: _cellBloc.state.content); - _focusNode = FocusNode(); - _focusNode.addListener(() { - widget.onFocus.value = _focusNode.hasFocus; - focusChanged(); - }); + _focusNode = CellSingleFocusNode(); + _listenFocusNode(); super.initState(); } @@ -72,10 +69,19 @@ class _NumberCellState extends State { widget.requestFocus.removeAllListener(); _delayOperation?.cancel(); _cellBloc.close(); + _focusNode.removeSingleListener(); _focusNode.dispose(); super.dispose(); } + @override + void didUpdateWidget(covariant NumberCell oldWidget) { + if (oldWidget != widget) { + _listenFocusNode(); + } + super.didUpdateWidget(oldWidget); + } + Future focusChanged() async { if (mounted) { _delayOperation?.cancel(); @@ -92,6 +98,14 @@ class _NumberCellState extends State { } } + void _listenFocusNode() { + widget.onFocus.value = _focusNode.hasFocus; + _focusNode.setSingleListener(() { + widget.onFocus.value = _focusNode.hasFocus; + focusChanged(); + }); + } + void _listenCellRequestFocus(BuildContext context) { widget.requestFocus.addListener(() { if (_focusNode.hasFocus == false && _focusNode.canRequestFocus) { diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart index affcacafd2ee8..14ace02d28333 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart @@ -35,9 +35,7 @@ class GridTextCell extends GridCellWidget { class _GridTextCellState extends State { late TextCellBloc _cellBloc; late TextEditingController _controller; - late FocusNode _focusNode; - - VoidCallback? _focusNodeListener; + late CellSingleFocusNode _focusNode; Timer? _delayOperation; @override @@ -46,44 +44,37 @@ class _GridTextCellState extends State { _cellBloc = getIt(param1: cellContext); _cellBloc.add(const TextCellEvent.initial()); _controller = TextEditingController(text: _cellBloc.state.content); - _focusNode = FocusNode(); - _focusNode.addListener(() { - widget.onFocus.value = _focusNode.hasFocus; - focusChanged(); - }); + _focusNode = CellSingleFocusNode(); + _listenFocusNode(); + _listenRequestFocus(context); super.initState(); } @override Widget build(BuildContext context) { - _listenCellRequestFocus(context); - return BlocProvider.value( value: _cellBloc, - child: BlocConsumer( + child: BlocListener( listener: (context, state) { if (_controller.text != state.content) { _controller.text = state.content; } }, - buildWhen: (previous, current) => previous.content != current.content, - builder: (context, state) { - return TextField( - controller: _controller, - focusNode: _focusNode, - onChanged: (value) => focusChanged(), - onEditingComplete: () => _focusNode.unfocus(), - maxLines: null, - style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), - decoration: InputDecoration( - contentPadding: EdgeInsets.zero, - border: InputBorder.none, - hintText: widget.cellStyle?.placeholder, - isDense: true, - ), - ); - }, + child: TextField( + controller: _controller, + focusNode: _focusNode, + onChanged: (value) => focusChanged(), + onEditingComplete: () => _focusNode.unfocus(), + maxLines: null, + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), + decoration: InputDecoration( + contentPadding: EdgeInsets.zero, + border: InputBorder.none, + hintText: widget.cellStyle?.placeholder, + isDense: true, + ), + ), ), ); } @@ -93,17 +84,29 @@ class _GridTextCellState extends State { widget.requestFocus.removeAllListener(); _delayOperation?.cancel(); _cellBloc.close(); + _focusNode.removeSingleListener(); _focusNode.dispose(); + super.dispose(); } @override void didUpdateWidget(covariant GridTextCell oldWidget) { - // TODO: implement didUpdateWidget + if (oldWidget != widget) { + _listenFocusNode(); + } super.didUpdateWidget(oldWidget); } - void _listenCellRequestFocus(BuildContext context) { + void _listenFocusNode() { + widget.onFocus.value = _focusNode.hasFocus; + _focusNode.setSingleListener(() { + widget.onFocus.value = _focusNode.hasFocus; + focusChanged(); + }); + } + + void _listenRequestFocus(BuildContext context) { widget.requestFocus.addListener(() { if (_focusNode.hasFocus == false && _focusNode.canRequestFocus) { FocusScope.of(context).requestFocus(_focusNode); 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 68908f1243d38..200a079d55797 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 @@ -4,6 +4,7 @@ import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/p import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:provider/provider.dart'; @@ -153,14 +154,14 @@ class _RowCells extends StatelessWidget { @override Widget build(BuildContext context) { return BlocBuilder( - buildWhen: (previous, current) => previous.cellDataMap != current.cellDataMap, + buildWhen: (previous, current) => !listEquals(previous.snapshots, current.snapshots), builder: (context, state) { return IntrinsicHeight( child: Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch, - children: _makeCells(context, state.cellDataMap), + children: _makeCells(context, state.gridCellMap), )); }, );