Pārlūkot izejas kodu

Merge pull request #1001 from AppFlowy-IO/merge/release_005

Merge/release 005
Nathan.fooo 2 gadi atpakaļ
vecāks
revīzija
3d2bfcc7c1
20 mainītis faili ar 280 papildinājumiem un 246 dzēšanām
  1. 0 1
      frontend/app_flowy/lib/plugins/board/application/board_bloc.dart
  2. 31 18
      frontend/app_flowy/lib/plugins/board/application/card/card_bloc.dart
  3. 10 1
      frontend/app_flowy/lib/plugins/board/presentation/board_page.dart
  4. 38 11
      frontend/app_flowy/lib/plugins/board/presentation/card/board_cell.dart
  5. 11 4
      frontend/app_flowy/lib/plugins/board/presentation/card/board_date_cell.dart
  6. 10 3
      frontend/app_flowy/lib/plugins/board/presentation/card/board_number_cell.dart
  7. 12 32
      frontend/app_flowy/lib/plugins/board/presentation/card/board_select_option_cell.dart
  8. 49 38
      frontend/app_flowy/lib/plugins/board/presentation/card/board_text_cell.dart
  9. 15 8
      frontend/app_flowy/lib/plugins/board/presentation/card/board_url_cell.dart
  10. 28 8
      frontend/app_flowy/lib/plugins/board/presentation/card/card.dart
  11. 0 2
      frontend/app_flowy/lib/plugins/board/presentation/card/card_cell_builder.dart
  12. 8 1
      frontend/app_flowy/lib/plugins/board/presentation/card/card_container.dart
  13. 2 1
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/date_cell/date_cell.dart
  14. 9 9
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart
  15. 1 1
      frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/footer.dart
  16. 3 1
      frontend/rust-lib/flowy-grid/src/services/group/action.rs
  17. 2 5
      frontend/rust-lib/flowy-grid/src/services/group/configuration.rs
  18. 8 2
      frontend/rust-lib/flowy-grid/src/services/group/controller.rs
  19. 4 0
      frontend/rust-lib/flowy-grid/src/services/group/controller_impls/checkbox_controller.rs
  20. 39 100
      frontend/rust-lib/flowy-grid/src/util.rs

+ 0 - 1
frontend/app_flowy/lib/plugins/board/application/board_bloc.dart

@@ -99,7 +99,6 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
             ));
           },
           endEditRow: (rowId) {
-            assert(state.editingRow.isSome());
             state.editingRow.fold(() => null, (editingRow) {
               assert(editingRow.row.id == rowId);
               emit(state.copyWith(editingRow: none()));

+ 31 - 18
frontend/app_flowy/lib/plugins/board/application/card/card_bloc.dart

@@ -7,20 +7,20 @@ import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';
-
 import 'card_data_controller.dart';
 
 part 'card_bloc.freezed.dart';
 
 class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
-  final String fieldId;
+  final String groupFieldId;
   final RowFFIService _rowService;
   final CardDataController _dataController;
 
   BoardCardBloc({
-    required this.fieldId,
+    required this.groupFieldId,
     required String gridId,
     required CardDataController dataController,
+    required bool isEditing,
   })  : _rowService = RowFFIService(
           gridId: gridId,
           blockId: dataController.rowPB.blockId,
@@ -29,7 +29,8 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
         super(
           BoardCardState.initial(
             dataController.rowPB,
-            _makeCells(fieldId, dataController.loadData()),
+            _makeCells(groupFieldId, dataController.loadData()),
+            isEditing,
           ),
         ) {
     on<BoardCardEvent>(
@@ -44,6 +45,9 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
               changeReason: reason,
             ));
           },
+          setIsEditing: (bool isEditing) {
+            emit(state.copyWith(isEditing: isEditing));
+          },
         );
       },
     );
@@ -69,7 +73,7 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
     _dataController.addListener(
       onRowChanged: (cellMap, reason) {
         if (!isClosed) {
-          final cells = _makeCells(fieldId, cellMap);
+          final cells = _makeCells(groupFieldId, cellMap);
           add(BoardCardEvent.didReceiveCells(cells, reason));
         }
       },
@@ -77,22 +81,24 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
   }
 }
 
-UnmodifiableListView<BoardCellEquatable> _makeCells(
-    String fieldId, GridCellMap originalCellMap) {
+List<BoardCellEquatable> _makeCells(
+    String groupFieldId, GridCellMap originalCellMap) {
   List<BoardCellEquatable> cells = [];
   for (final entry in originalCellMap.entries) {
-    if (entry.value.fieldId != fieldId) {
+    // Filter out the cell if it's fieldId equal to the groupFieldId
+    if (entry.value.fieldId != groupFieldId) {
       cells.add(BoardCellEquatable(entry.value));
     }
   }
-  return UnmodifiableListView(cells);
+  return cells;
 }
 
 @freezed
 class BoardCardEvent with _$BoardCardEvent {
   const factory BoardCardEvent.initial() = _InitialRow;
+  const factory BoardCardEvent.setIsEditing(bool isEditing) = _IsEditing;
   const factory BoardCardEvent.didReceiveCells(
-    UnmodifiableListView<BoardCellEquatable> cells,
+    List<BoardCellEquatable> cells,
     RowsChangedReason reason,
   ) = _DidReceiveCells;
 }
@@ -101,15 +107,20 @@ class BoardCardEvent with _$BoardCardEvent {
 class BoardCardState with _$BoardCardState {
   const factory BoardCardState({
     required RowPB rowPB,
-    required UnmodifiableListView<BoardCellEquatable> cells,
+    required List<BoardCellEquatable> cells,
+    required bool isEditing,
     RowsChangedReason? changeReason,
   }) = _BoardCardState;
 
   factory BoardCardState.initial(
-          RowPB rowPB, UnmodifiableListView<BoardCellEquatable> cells) =>
+    RowPB rowPB,
+    List<BoardCellEquatable> cells,
+    bool isEditing,
+  ) =>
       BoardCardState(
         rowPB: rowPB,
         cells: cells,
+        isEditing: isEditing,
       );
 }
 
@@ -119,10 +130,12 @@ class BoardCellEquatable extends Equatable {
   const BoardCellEquatable(this.identifier);
 
   @override
-  List<Object?> get props => [
-        identifier.fieldContext.id,
-        identifier.fieldContext.fieldType,
-        identifier.fieldContext.visibility,
-        identifier.fieldContext.width,
-      ];
+  List<Object?> get props {
+    return [
+      identifier.fieldContext.id,
+      identifier.fieldContext.fieldType,
+      identifier.fieldContext.visibility,
+      identifier.fieldContext.width,
+    ];
+  }
 }

+ 10 - 1
frontend/app_flowy/lib/plugins/board/presentation/board_page.dart

@@ -64,6 +64,7 @@ class BoardContent extends StatefulWidget {
 
 class _BoardContentState extends State<BoardContent> {
   late AppFlowyBoardScrollController scrollManager;
+  final Map<String, ValueKey> cardKeysCache = {};
 
   final config = AppFlowyBoardConfig(
     groupBackgroundColor: HexColor.fromHex('#F7F8FC'),
@@ -83,6 +84,7 @@ class _BoardContentState extends State<BoardContent> {
         buildWhen: (previous, current) => previous.groupIds != current.groupIds,
         builder: (context, state) {
           final column = Column(
+            crossAxisAlignment: CrossAxisAlignment.start,
             children: [const _ToolbarBlocAdaptor(), _buildBoard(context)],
           );
 
@@ -240,8 +242,15 @@ class _BoardContentState extends State<BoardContent> {
       },
     );
 
+    ValueKey? key = cardKeysCache[columnItem.id];
+    if (key == null) {
+      final newKey = ValueKey(columnItem.id);
+      cardKeysCache[columnItem.id] = newKey;
+      key = newKey;
+    }
+
     return AppFlowyGroupCard(
-      key: ValueKey(columnItem.id),
+      key: key,
       margin: config.cardPadding,
       decoration: _makeBoxDecoration(context),
       child: BoardCard(

+ 38 - 11
frontend/app_flowy/lib/plugins/board/presentation/card/board_cell.dart

@@ -1,47 +1,74 @@
 import 'package:app_flowy/plugins/grid/application/prelude.dart';
-import 'package:flowy_infra/notifier.dart';
+import 'package:flutter/material.dart';
 
 abstract class FocusableBoardCell {
   set becomeFocus(bool isFocus);
 }
 
 class EditableCellNotifier {
-  final Notifier becomeFirstResponder = Notifier();
+  final ValueNotifier<bool> isCellEditing;
 
-  final Notifier resignFirstResponder = Notifier();
+  EditableCellNotifier({bool isEditing = false})
+      : isCellEditing = ValueNotifier(isEditing);
 
-  EditableCellNotifier();
+  void dispose() {
+    isCellEditing.dispose();
+  }
 }
 
 class EditableRowNotifier {
   final Map<EditableCellId, EditableCellNotifier> _cells = {};
+  final ValueNotifier<bool> isEditing;
+
+  EditableRowNotifier({required bool isEditing})
+      : isEditing = ValueNotifier(isEditing);
 
   void insertCell(
     GridCellIdentifier cellIdentifier,
     EditableCellNotifier notifier,
   ) {
+    assert(
+      _cells.values.isEmpty,
+      'Only one cell can receive the notification',
+    );
+    final id = EditableCellId.from(cellIdentifier);
+    _cells[id]?.dispose();
+
+    notifier.isCellEditing.addListener(() {
+      isEditing.value = notifier.isCellEditing.value;
+    });
+
     _cells[EditableCellId.from(cellIdentifier)] = notifier;
   }
 
   void becomeFirstResponder() {
-    for (final notifier in _cells.values) {
-      notifier.becomeFirstResponder.notify();
-    }
+    if (_cells.values.isEmpty) return;
+    assert(
+      _cells.values.length == 1,
+      'Only one cell can receive the notification',
+    );
+    _cells.values.first.isCellEditing.value = true;
   }
 
   void resignFirstResponder() {
-    for (final notifier in _cells.values) {
-      notifier.resignFirstResponder.notify();
-    }
+    if (_cells.values.isEmpty) return;
+    assert(
+      _cells.values.length == 1,
+      'Only one cell can receive the notification',
+    );
+    _cells.values.first.isCellEditing.value = false;
   }
 
   void clear() {
+    for (final notifier in _cells.values) {
+      notifier.dispose();
+    }
     _cells.clear();
   }
 
   void dispose() {
     for (final notifier in _cells.values) {
-      notifier.resignFirstResponder.notify();
+      notifier.dispose();
     }
 
     _cells.clear();

+ 11 - 4
frontend/app_flowy/lib/plugins/board/presentation/card/board_date_cell.dart

@@ -5,6 +5,8 @@ import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
+import 'define.dart';
+
 class BoardDateCell extends StatefulWidget {
   final String groupId;
   final GridCellControllerBuilder cellControllerBuilder;
@@ -44,10 +46,15 @@ class _BoardDateCellState extends State<BoardDateCell> {
           } else {
             return Align(
               alignment: Alignment.centerLeft,
-              child: FlowyText.regular(
-                state.dateStr,
-                fontSize: 13,
-                color: context.read<AppTheme>().shader3,
+              child: Padding(
+                padding: EdgeInsets.symmetric(
+                  vertical: BoardSizes.cardCellVPadding,
+                ),
+                child: FlowyText.regular(
+                  state.dateStr,
+                  fontSize: 13,
+                  color: context.read<AppTheme>().shader3,
+                ),
               ),
             );
           }

+ 10 - 3
frontend/app_flowy/lib/plugins/board/presentation/card/board_number_cell.dart

@@ -4,6 +4,8 @@ import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
+import 'define.dart';
+
 class BoardNumberCell extends StatefulWidget {
   final String groupId;
   final GridCellControllerBuilder cellControllerBuilder;
@@ -43,9 +45,14 @@ class _BoardNumberCellState extends State<BoardNumberCell> {
           } else {
             return Align(
               alignment: Alignment.centerLeft,
-              child: FlowyText.medium(
-                state.content,
-                fontSize: 14,
+              child: Padding(
+                padding: EdgeInsets.symmetric(
+                  vertical: BoardSizes.cardCellVPadding,
+                ),
+                child: FlowyText.medium(
+                  state.content,
+                  fontSize: 14,
+                ),
               ),
             );
           }

+ 12 - 32
frontend/app_flowy/lib/plugins/board/presentation/card/board_select_option_cell.dart

@@ -56,23 +56,23 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
                   (option) => SelectOptionTag.fromOption(
                     context: context,
                     option: option,
+                    onSelected: () {
+                      SelectOptionCellEditor.show(
+                        context: context,
+                        cellController: widget.cellControllerBuilder.build()
+                            as GridSelectOptionCellController,
+                      );
+                    },
                   ),
                 )
                 .toList();
 
             return IntrinsicHeight(
-              child: Stack(
-                alignment: AlignmentDirectional.center,
-                fit: StackFit.expand,
-                children: [
-                  Padding(
-                    padding: const EdgeInsets.symmetric(vertical: 6),
-                    child: Wrap(spacing: 4, runSpacing: 2, children: children),
-                  ),
-                  _SelectOptionDialog(
-                    controller: widget.cellControllerBuilder.build(),
-                  ),
-                ],
+              child: Padding(
+                padding: const EdgeInsets.symmetric(vertical: 6),
+                child: SizedBox.expand(
+                  child: Wrap(spacing: 4, runSpacing: 2, children: children),
+                ),
               ),
             );
           }
@@ -87,23 +87,3 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
     super.dispose();
   }
 }
-
-class _SelectOptionDialog extends StatelessWidget {
-  final GridSelectOptionCellController _controller;
-  const _SelectOptionDialog({
-    Key? key,
-    required IGridCellController controller,
-  })  : _controller = controller as GridSelectOptionCellController,
-        super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    return InkWell(onTap: () {
-      SelectOptionCellEditor.show(
-        context,
-        _controller,
-        () {},
-      );
-    });
-  }
-}

+ 49 - 38
frontend/app_flowy/lib/plugins/board/presentation/card/board_text_cell.dart

@@ -9,7 +9,6 @@ import 'define.dart';
 
 class BoardTextCell extends StatefulWidget with EditableCell {
   final String groupId;
-  final bool isFocus;
   @override
   final EditableCellNotifier? editableNotifier;
   final GridCellControllerBuilder cellControllerBuilder;
@@ -18,7 +17,6 @@ class BoardTextCell extends StatefulWidget with EditableCell {
     required this.groupId,
     required this.cellControllerBuilder,
     this.editableNotifier,
-    this.isFocus = false,
     Key? key,
   }) : super(key: key);
 
@@ -39,38 +37,40 @@ class _BoardTextCellState extends State<BoardTextCell> {
     _cellBloc = BoardTextCellBloc(cellController: cellController)
       ..add(const BoardTextCellEvent.initial());
     _controller = TextEditingController(text: _cellBloc.state.content);
-    focusWhenInit = widget.isFocus;
-
-    if (widget.isFocus) {
+    focusWhenInit = widget.editableNotifier?.isCellEditing.value ?? false;
+    if (focusWhenInit) {
       focusNode.requestFocus();
     }
 
     focusNode.addListener(() {
       if (!focusNode.hasFocus) {
+        focusWhenInit = false;
+        widget.editableNotifier?.isCellEditing.value = false;
         _cellBloc.add(const BoardTextCellEvent.enableEdit(false));
-
-        if (focusWhenInit) {
-          setState(() {
-            focusWhenInit = false;
-          });
-        }
       }
     });
+    _bindEditableNotifier();
+    super.initState();
+  }
 
-    widget.editableNotifier?.becomeFirstResponder.addListener(() {
+  void _bindEditableNotifier() {
+    widget.editableNotifier?.isCellEditing.addListener(() {
       if (!mounted) return;
-      WidgetsBinding.instance.addPostFrameCallback((_) {
-        focusNode.requestFocus();
-      });
-      _cellBloc.add(const BoardTextCellEvent.enableEdit(true));
-    });
 
-    widget.editableNotifier?.resignFirstResponder.addListener(() {
-      if (!mounted) return;
-      _cellBloc.add(const BoardTextCellEvent.enableEdit(false));
+      final isEditing = widget.editableNotifier?.isCellEditing.value ?? false;
+      if (isEditing) {
+        WidgetsBinding.instance.addPostFrameCallback((_) {
+          focusNode.requestFocus();
+        });
+      }
+      _cellBloc.add(BoardTextCellEvent.enableEdit(isEditing));
     });
+  }
 
-    super.initState();
+  @override
+  void didUpdateWidget(covariant BoardTextCell oldWidget) {
+    _bindEditableNotifier();
+    super.didUpdateWidget(oldWidget);
   }
 
   @override
@@ -84,6 +84,15 @@ class _BoardTextCellState extends State<BoardTextCell> {
           }
         },
         child: BlocBuilder<BoardTextCellBloc, BoardTextCellState>(
+          buildWhen: (previous, current) {
+            if (previous.content != current.content &&
+                _controller.text == current.content &&
+                current.enableEdit) {
+              return false;
+            }
+
+            return previous != current;
+          },
           builder: (context, state) {
             if (state.content.isEmpty &&
                 state.enableEdit == false &&
@@ -127,24 +136,26 @@ class _BoardTextCellState extends State<BoardTextCell> {
   }
 
   Widget _buildTextField() {
-    return TextField(
-      controller: _controller,
-      focusNode: focusNode,
-      onChanged: (value) => focusChanged(),
-      onEditingComplete: () => focusNode.unfocus(),
-      maxLines: 1,
-      style: const TextStyle(
-        fontSize: 14,
-        fontWeight: FontWeight.w500,
-        fontFamily: 'Mulish',
-      ),
-      decoration: InputDecoration(
-        // Magic number 4 makes the textField take up the same space as FlowyText
-        contentPadding: EdgeInsets.symmetric(
-          vertical: BoardSizes.cardCellVPadding + 4,
+    return IntrinsicHeight(
+      child: TextField(
+        controller: _controller,
+        focusNode: focusNode,
+        onChanged: (value) => focusChanged(),
+        onEditingComplete: () => focusNode.unfocus(),
+        maxLines: null,
+        style: const TextStyle(
+          fontSize: 14,
+          fontWeight: FontWeight.w500,
+          fontFamily: 'Mulish',
+        ),
+        decoration: InputDecoration(
+          // Magic number 4 makes the textField take up the same space as FlowyText
+          contentPadding: EdgeInsets.symmetric(
+            vertical: BoardSizes.cardCellVPadding + 4,
+          ),
+          border: InputBorder.none,
+          isDense: true,
         ),
-        border: InputBorder.none,
-        isDense: true,
       ),
     );
   }

+ 15 - 8
frontend/app_flowy/lib/plugins/board/presentation/card/board_url_cell.dart

@@ -4,6 +4,8 @@ import 'package:flowy_infra/theme.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
+import 'define.dart';
+
 class BoardUrlCell extends StatefulWidget {
   final String groupId;
   final GridCellControllerBuilder cellControllerBuilder;
@@ -43,14 +45,19 @@ class _BoardUrlCellState extends State<BoardUrlCell> {
           } else {
             return Align(
               alignment: Alignment.centerLeft,
-              child: RichText(
-                textAlign: TextAlign.left,
-                text: TextSpan(
-                  text: state.content,
-                  style: TextStyle(
-                    color: theme.main2,
-                    fontSize: 14,
-                    decoration: TextDecoration.underline,
+              child: Padding(
+                padding: EdgeInsets.symmetric(
+                  vertical: BoardSizes.cardCellVPadding,
+                ),
+                child: RichText(
+                  textAlign: TextAlign.left,
+                  text: TextSpan(
+                    text: state.content,
+                    style: TextStyle(
+                      color: theme.main2,
+                      fontSize: 14,
+                      decoration: TextDecoration.underline,
+                    ),
                   ),
                 ),
               ),

+ 28 - 8
frontend/app_flowy/lib/plugins/board/presentation/card/card.dart

@@ -5,6 +5,7 @@ import 'package:app_flowy/plugins/grid/presentation/widgets/row/row_action_sheet
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
+import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'board_cell.dart';
@@ -41,12 +42,19 @@ class _BoardCardState extends State<BoardCard> {
 
   @override
   void initState() {
-    rowNotifier = EditableRowNotifier();
+    rowNotifier = EditableRowNotifier(isEditing: widget.isEditing);
     _cardBloc = BoardCardBloc(
       gridId: widget.gridId,
-      fieldId: widget.fieldId,
+      groupFieldId: widget.fieldId,
       dataController: widget.dataController,
+      isEditing: widget.isEditing,
     )..add(const BoardCardEvent.initial());
+
+    rowNotifier.isEditing.addListener(() {
+      if (!mounted) return;
+      _cardBloc.add(BoardCardEvent.setIsEditing(rowNotifier.isEditing.value));
+    });
+
     super.initState();
   }
 
@@ -56,10 +64,15 @@ class _BoardCardState extends State<BoardCard> {
       value: _cardBloc,
       child: BlocBuilder<BoardCardBloc, BoardCardState>(
         buildWhen: (previous, current) {
-          return previous.cells.length != current.cells.length;
+          if (previous.cells.length != current.cells.length ||
+              previous.isEditing != current.isEditing) {
+            return true;
+          }
+          return !listEquals(previous.cells, current.cells);
         },
         builder: (context, state) {
           return BoardCardContainer(
+            buildAccessoryWhen: () => state.isEditing == false,
             accessoryBuilder: (context) {
               return [
                 _CardEditOption(
@@ -92,17 +105,24 @@ class _BoardCardState extends State<BoardCard> {
     rowNotifier.clear();
     cells.asMap().forEach(
       (int index, GridCellIdentifier cellId) {
-        final cellNotifier = EditableCellNotifier();
+        EditableCellNotifier cellNotifier;
+        if (index == 0) {
+          // Only use the first cell to receive user's input when click the edit
+          // button
+          cellNotifier = EditableCellNotifier(
+            isEditing: rowNotifier.isEditing.value,
+          );
+          rowNotifier.insertCell(cellId, cellNotifier);
+        } else {
+          cellNotifier = EditableCellNotifier();
+        }
+
         Widget child = widget.cellBuilder.buildCell(
           widget.groupId,
           cellId,
-          index == 0 ? widget.isEditing : false,
           cellNotifier,
         );
 
-        if (index == 0) {
-          rowNotifier.insertCell(cellId, cellNotifier);
-        }
         child = Padding(
           key: cellId.key(),
           padding: const EdgeInsets.only(left: 4, right: 4),

+ 0 - 2
frontend/app_flowy/lib/plugins/board/presentation/card/card_cell_builder.dart

@@ -23,7 +23,6 @@ class BoardCellBuilder {
   Widget buildCell(
     String groupId,
     GridCellIdentifier cellId,
-    bool isEditing,
     EditableCellNotifier cellNotifier,
   ) {
     final cellControllerBuilder = GridCellControllerBuilder(
@@ -69,7 +68,6 @@ class BoardCellBuilder {
         return BoardTextCell(
           groupId: groupId,
           cellControllerBuilder: cellControllerBuilder,
-          isFocus: isEditing,
           editableNotifier: cellNotifier,
           key: key,
         );

+ 8 - 1
frontend/app_flowy/lib/plugins/board/presentation/card/card_container.dart

@@ -7,11 +7,13 @@ import 'package:styled_widget/styled_widget.dart';
 class BoardCardContainer extends StatelessWidget {
   final Widget child;
   final CardAccessoryBuilder? accessoryBuilder;
+  final bool Function()? buildAccessoryWhen;
   final void Function(BuildContext) onTap;
   const BoardCardContainer({
     required this.child,
     required this.onTap,
     this.accessoryBuilder,
+    this.buildAccessoryWhen,
     Key? key,
   }) : super(key: key);
 
@@ -22,7 +24,12 @@ class BoardCardContainer extends StatelessWidget {
       child: Consumer<_CardContainerNotifier>(
         builder: (context, notifier, _) {
           Widget container = Center(child: child);
-          if (accessoryBuilder != null) {
+          bool shouldBuildAccessory = true;
+          if (buildAccessoryWhen != null) {
+            shouldBuildAccessory = buildAccessoryWhen!.call();
+          }
+
+          if (accessoryBuilder != null && shouldBuildAccessory) {
             final accessories = accessoryBuilder!(context);
             if (accessories.isNotEmpty) {
               container = _CardEnterRegion(

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

@@ -46,7 +46,8 @@ class _DateCellState extends GridCellState<GridDateCell> {
   @override
   void initState() {
     _popover = PopoverController();
-    final cellController = widget.cellControllerBuilder.build();
+    final cellController =
+        widget.cellControllerBuilder.build() as GridDateCellController;
     _cellBloc = getIt<DateCellBloc>(param1: cellController)
       ..add(const DateCellEvent.initial());
     super.initState();

+ 9 - 9
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart

@@ -27,13 +27,13 @@ const double _editorPannelWidth = 300;
 
 class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
   final GridSelectOptionCellController cellController;
-  final VoidCallback onDismissed;
+  final VoidCallback? onDismissed;
 
   static double editorPanelWidth = 300;
 
   const SelectOptionCellEditor({
     required this.cellController,
-    required this.onDismissed,
+    this.onDismissed,
     Key? key,
   }) : super(key: key);
 
@@ -61,14 +61,14 @@ class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
     );
   }
 
-  static void show(
-    BuildContext context,
-    GridSelectOptionCellController cellContext,
-    VoidCallback onDismissed,
-  ) {
+  static void show({
+    required BuildContext context,
+    required GridSelectOptionCellController cellController,
+    VoidCallback? onDismissed,
+  }) {
     SelectOptionCellEditor.remove(context);
     final editor = SelectOptionCellEditor(
-      cellController: cellContext,
+      cellController: cellController,
       onDismissed: onDismissed,
     );
 
@@ -97,7 +97,7 @@ class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
   bool asBarrier() => true;
 
   @override
-  void didRemove() => onDismissed();
+  void didRemove() => onDismissed?.call();
 }
 
 class _OptionList extends StatelessWidget {

+ 1 - 1
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/footer.dart

@@ -25,7 +25,7 @@ class AppFlowyGroupFooter extends StatefulWidget {
 class _AppFlowyGroupFooterState extends State<AppFlowyGroupFooter> {
   @override
   Widget build(BuildContext context) {
-    return GestureDetector(
+    return InkWell(
       onTap: widget.onAddButtonClick,
       child: SizedBox(
         height: widget.height,

+ 3 - 1
frontend/rust-lib/flowy-grid/src/services/group/action.rs

@@ -8,7 +8,9 @@ pub trait GroupAction: Send + Sync {
     fn default_cell_rev(&self) -> Option<CellRevision> {
         None
     }
-
+    fn use_default_group(&self) -> bool {
+        true
+    }
     fn can_group(&self, content: &str, cell_data: &Self::CellDataType) -> bool;
     fn add_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB>;
     fn remove_row_if_match(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB>;

+ 2 - 5
frontend/rust-lib/flowy-grid/src/services/group/configuration.rs

@@ -97,11 +97,8 @@ where
         self.groups_map.values().collect()
     }
 
-    /// Returns the all the groups that contain the default group.
-    pub(crate) fn clone_groups(&self) -> Vec<Group> {
-        let mut groups: Vec<Group> = self.groups_map.values().cloned().collect();
-        groups.push(self.default_group.clone());
-        groups
+    pub(crate) fn default_group(&self) -> &Group {
+        &self.default_group
     }
 
     /// Iterate mut the groups. The default group will be the last one that get mutated.

+ 8 - 2
frontend/rust-lib/flowy-grid/src/services/group/controller.rs

@@ -182,7 +182,13 @@ where
     }
 
     fn groups(&self) -> Vec<Group> {
-        self.group_ctx.clone_groups()
+        if self.use_default_group() {
+            let mut groups: Vec<Group> = self.group_ctx.concrete_groups().into_iter().cloned().collect();
+            groups.push(self.group_ctx.default_group().clone());
+            groups
+        } else {
+            self.group_ctx.concrete_groups().into_iter().cloned().collect()
+        }
     }
 
     fn get_group(&self, group_id: &str) -> Option<(usize, Group)> {
@@ -243,7 +249,7 @@ where
             let cell_data = cell_bytes.parser::<P>()?;
             let mut changesets = self.add_row_if_match(row_rev, &cell_data);
             let default_group_changeset = self.update_default_group(row_rev, &changesets);
-            tracing::info!("default_group_changeset: {}", default_group_changeset);
+            tracing::trace!("default_group_changeset: {}", default_group_changeset);
             if !default_group_changeset.is_empty() {
                 changesets.push(default_group_changeset);
             }

+ 4 - 0
frontend/rust-lib/flowy-grid/src/services/group/controller_impls/checkbox_controller.rs

@@ -27,6 +27,10 @@ impl GroupAction for CheckboxGroupController {
         Some(CellRevision::new(UNCHECK.to_string()))
     }
 
+    fn use_default_group(&self) -> bool {
+        false
+    }
+
     fn can_group(&self, content: &str, cell_data: &Self::CellDataType) -> bool {
         if cell_data.is_check() {
             content == CHECK

+ 39 - 100
frontend/rust-lib/flowy-grid/src/util.rs

@@ -3,7 +3,6 @@ use crate::services::field::*;
 use crate::services::row::RowRevisionBuilder;
 use flowy_grid_data_model::revision::BuildGridContext;
 use flowy_sync::client_grid::GridBuilder;
-use lib_infra::util::timestamp;
 
 pub fn make_default_grid() -> BuildGridContext {
     let mut grid_builder = GridBuilder::new();
@@ -59,6 +58,45 @@ pub fn make_default_board() -> BuildGridContext {
     let single_select_field_id = single_select_field.id.clone();
     grid_builder.add_field(single_select_field);
 
+    for i in 0..3 {
+        let mut row_builder = RowRevisionBuilder::new(grid_builder.block_id(), grid_builder.field_revs());
+        row_builder.insert_select_option_cell(&single_select_field_id, to_do_option.id.clone());
+        let data = format!("Card {}", i + 1);
+        row_builder.insert_text_cell(&text_field_id, data);
+        let row = row_builder.build();
+        grid_builder.add_row(row);
+    }
+
+    grid_builder.build()
+}
+
+#[allow(dead_code)]
+pub fn make_default_board_2() -> BuildGridContext {
+    let mut grid_builder = GridBuilder::new();
+    // text
+    let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
+        .name("Description")
+        .visibility(true)
+        .primary(true)
+        .build();
+    let text_field_id = text_field.id.clone();
+    grid_builder.add_field(text_field);
+
+    // single select
+    let to_do_option = SelectOptionPB::with_color("To Do", SelectOptionColorPB::Purple);
+    let doing_option = SelectOptionPB::with_color("Doing", SelectOptionColorPB::Orange);
+    let done_option = SelectOptionPB::with_color("Done", SelectOptionColorPB::Yellow);
+    let single_select_type_option = SingleSelectTypeOptionBuilder::default()
+        .add_option(to_do_option.clone())
+        .add_option(doing_option.clone())
+        .add_option(done_option.clone());
+    let single_select_field = FieldBuilder::new(single_select_type_option)
+        .name("Status")
+        .visibility(true)
+        .build();
+    let single_select_field_id = single_select_field.id.clone();
+    grid_builder.add_field(single_select_field);
+
     // MultiSelect
     let work_option = SelectOptionPB::with_color("Work", SelectOptionColorPB::Aqua);
     let travel_option = SelectOptionPB::with_color("Travel", SelectOptionColorPB::Green);
@@ -152,102 +190,3 @@ pub fn make_default_board() -> BuildGridContext {
 
     grid_builder.build()
 }
-
-#[allow(dead_code)]
-pub fn make_default_board2() -> BuildGridContext {
-    let mut grid_builder = GridBuilder::new();
-    // text
-    let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
-        .name("Name")
-        .visibility(true)
-        .primary(true)
-        .build();
-    let text_field_id = text_field.id.clone();
-    grid_builder.add_field(text_field);
-
-    // date
-    let date_type_option = DateTypeOptionBuilder::default();
-    let date_field = FieldBuilder::new(date_type_option)
-        .name("Date")
-        .visibility(true)
-        .build();
-    let date_field_id = date_field.id.clone();
-    let timestamp = timestamp();
-    grid_builder.add_field(date_field);
-
-    // single select
-    let in_progress_option = SelectOptionPB::new("In progress");
-    let not_started_option = SelectOptionPB::new("Not started");
-    let done_option = SelectOptionPB::new("Done");
-    let single_select_type_option = SingleSelectTypeOptionBuilder::default()
-        .add_option(not_started_option.clone())
-        .add_option(in_progress_option)
-        .add_option(done_option);
-    let single_select_field = FieldBuilder::new(single_select_type_option)
-        .name("Status")
-        .visibility(true)
-        .build();
-    let single_select_field_id = single_select_field.id.clone();
-    grid_builder.add_field(single_select_field);
-
-    // MultiSelect
-    let apple_option = SelectOptionPB::new("Apple");
-    let banana_option = SelectOptionPB::new("Banana");
-    let pear_option = SelectOptionPB::new("Pear");
-    let multi_select_type_option = MultiSelectTypeOptionBuilder::default()
-        .add_option(banana_option.clone())
-        .add_option(apple_option.clone())
-        .add_option(pear_option);
-    let multi_select_field = FieldBuilder::new(multi_select_type_option)
-        .name("Fruit")
-        .visibility(true)
-        .build();
-    let multi_select_field_id = multi_select_field.id.clone();
-    grid_builder.add_field(multi_select_field);
-
-    // Number
-    let number_type_option = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD);
-    let number_field = FieldBuilder::new(number_type_option)
-        .name("Price")
-        .visibility(true)
-        .build();
-    let number_field_id = number_field.id.clone();
-    grid_builder.add_field(number_field);
-
-    // Checkbox
-    let checkbox_type_option = CheckboxTypeOptionBuilder::default();
-    let checkbox_field = FieldBuilder::new(checkbox_type_option).name("Reimbursement").build();
-    let checkbox_field_id = checkbox_field.id.clone();
-    grid_builder.add_field(checkbox_field);
-
-    // Url
-    let url_type_option = URLTypeOptionBuilder::default();
-    let url_field = FieldBuilder::new(url_type_option).name("Shop Link").build();
-    let url_field_id = url_field.id.clone();
-    grid_builder.add_field(url_field);
-
-    // Insert rows
-    for i in 0..10 {
-        // insert single select
-        let mut row_builder = RowRevisionBuilder::new(grid_builder.block_id(), grid_builder.field_revs());
-        row_builder.insert_select_option_cell(&single_select_field_id, not_started_option.id.clone());
-        // insert multi select
-        row_builder.insert_select_option_cell(&multi_select_field_id, apple_option.id.clone());
-        row_builder.insert_select_option_cell(&multi_select_field_id, banana_option.id.clone());
-        // insert text
-        row_builder.insert_text_cell(&text_field_id, format!("Card {}", i));
-        // insert date
-        row_builder.insert_date_cell(&date_field_id, timestamp);
-        // number
-        row_builder.insert_number_cell(&number_field_id, i);
-        // checkbox
-        row_builder.insert_checkbox_cell(&checkbox_field_id, i % 2 == 0);
-        // url
-        row_builder.insert_url_cell(&url_field_id, "https://appflowy.io".to_string());
-
-        let row = row_builder.build();
-        grid_builder.add_row(row);
-    }
-
-    grid_builder.build()
-}