浏览代码

Merge pull request #925 from AppFlowy-IO/feat/edit_card_directly

Feat/edit card directly
Nathan.fooo 2 年之前
父节点
当前提交
624de03897
共有 30 个文件被更改,包括 271 次插入107 次删除
  1. 14 5
      frontend/app_flowy/lib/plugins/board/application/board_bloc.dart
  2. 8 0
      frontend/app_flowy/lib/plugins/board/application/card/board_text_cell_bloc.dart
  3. 6 1
      frontend/app_flowy/lib/plugins/board/application/group_controller.dart
  4. 2 7
      frontend/app_flowy/lib/plugins/board/presentation/board_page.dart
  5. 3 0
      frontend/app_flowy/lib/plugins/board/presentation/card/board_cell.dart
  6. 35 6
      frontend/app_flowy/lib/plugins/board/presentation/card/board_select_option_cell.dart
  7. 35 15
      frontend/app_flowy/lib/plugins/board/presentation/card/board_text_cell.dart
  8. 26 11
      frontend/app_flowy/lib/plugins/board/presentation/card/card.dart
  9. 6 1
      frontend/app_flowy/lib/plugins/board/presentation/card/card_cell_builder.dart
  10. 4 2
      frontend/app_flowy/lib/plugins/grid/application/cell/cell_listener.dart
  11. 8 1
      frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_cache.dart
  12. 1 1
      frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/context_builder.dart
  13. 2 1
      frontend/app_flowy/lib/plugins/grid/application/row/row_cache.dart
  14. 4 0
      frontend/app_flowy/linux/flutter/generated_plugin_registrant.cc
  15. 1 0
      frontend/app_flowy/linux/flutter/generated_plugins.cmake
  16. 2 0
      frontend/app_flowy/macos/Flutter/GeneratedPluginRegistrant.swift
  17. 4 0
      frontend/app_flowy/packages/appflowy_board/CHANGELOG.md
  18. 48 25
      frontend/app_flowy/packages/appflowy_board/example/lib/multi_board_list_example.dart
  19. 2 2
      frontend/app_flowy/packages/appflowy_board/lib/src/utils/log.dart
  20. 9 4
      frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board.dart
  21. 2 2
      frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column.dart
  22. 5 0
      frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target.dart
  23. 2 1
      frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target_interceptor.dart
  24. 5 2
      frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart
  25. 20 16
      frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_controller.dart
  26. 1 1
      frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_state.dart
  27. 1 1
      frontend/app_flowy/packages/appflowy_board/pubspec.yaml
  28. 13 2
      frontend/rust-lib/flowy-grid/src/entities/block_entities.rs
  29. 1 0
      frontend/rust-lib/flowy-grid/src/services/block_manager.rs
  30. 1 0
      frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs

+ 14 - 5
frontend/app_flowy/lib/plugins/board/application/board_bloc.dart

@@ -142,7 +142,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
     for (final group in groups) {
       final delegate = GroupControllerDelegateImpl(
         controller: boardController,
-        didAddColumnItem: (groupId, row) {
+        onNewColumnItem: (groupId, row) {
           add(BoardEvent.didCreateRow(groupId, row));
         },
       );
@@ -313,11 +313,11 @@ class BoardColumnItem extends AFColumnItem {
 
 class GroupControllerDelegateImpl extends GroupControllerDelegate {
   final AFBoardDataController controller;
-  final void Function(String, RowPB) didAddColumnItem;
+  final void Function(String, RowPB) onNewColumnItem;
 
   GroupControllerDelegateImpl({
     required this.controller,
-    required this.didAddColumnItem,
+    required this.onNewColumnItem,
   });
 
   @override
@@ -329,10 +329,8 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate {
       final item = BoardColumnItem(
         row: row,
         fieldId: group.fieldId,
-        requestFocus: true,
       );
       controller.addColumnItem(group.groupId, item);
-      didAddColumnItem(group.groupId, row);
     }
   }
 
@@ -351,6 +349,17 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate {
       ),
     );
   }
+
+  @override
+  void addNewRow(GroupPB group, RowPB row) {
+    final item = BoardColumnItem(
+      row: row,
+      fieldId: group.fieldId,
+      requestFocus: true,
+    );
+    controller.addColumnItem(group.groupId, item);
+    onNewColumnItem(group.groupId, row);
+  }
 }
 
 class BoardEditingRow {

+ 8 - 0
frontend/app_flowy/lib/plugins/board/application/card/board_text_cell_bloc.dart

@@ -1,4 +1,5 @@
 import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
+import 'package:flutter/foundation.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';
@@ -20,6 +21,12 @@ class BoardTextCellBloc extends Bloc<BoardTextCellEvent, BoardTextCellState> {
           didReceiveCellUpdate: (content) {
             emit(state.copyWith(content: content));
           },
+          updateText: (text) {
+            if (text != state.content) {
+              cellController.saveCellData(text);
+              emit(state.copyWith(content: text));
+            }
+          },
         );
       },
     );
@@ -49,6 +56,7 @@ class BoardTextCellBloc extends Bloc<BoardTextCellEvent, BoardTextCellState> {
 @freezed
 class BoardTextCellEvent with _$BoardTextCellEvent {
   const factory BoardTextCellEvent.initial() = _InitialCell;
+  const factory BoardTextCellEvent.updateText(String text) = _UpdateContent;
   const factory BoardTextCellEvent.didReceiveCellUpdate(String cellContent) =
       _DidReceiveCellUpdate;
 }

+ 6 - 1
frontend/app_flowy/lib/plugins/board/application/group_controller.dart

@@ -9,6 +9,7 @@ abstract class GroupControllerDelegate {
   void removeRow(GroupPB group, String rowId);
   void insertRow(GroupPB group, RowPB row, int? index);
   void updateRow(GroupPB group, RowPB row);
+  void addNewRow(GroupPB group, RowPB row);
 }
 
 class GroupController {
@@ -48,7 +49,11 @@ class GroupController {
               group.rows.add(insertedRow.row);
             }
 
-            delegate.insertRow(group, insertedRow.row, index);
+            if (insertedRow.isNew) {
+              delegate.addNewRow(group, insertedRow.row);
+            } else {
+              delegate.insertRow(group, insertedRow.row, index);
+            }
           }
 
           for (final updatedRow in changeset.updatedRows) {

+ 2 - 7
frontend/app_flowy/lib/plugins/board/presentation/board_page.dart

@@ -196,10 +196,8 @@ class _BoardContentState extends State<BoardContent> {
     );
 
     final cellBuilder = BoardCellBuilder(cardController);
-    final isEditing = context.read<BoardBloc>().state.editingRow.fold(
-          () => false,
-          (editingRow) => editingRow.row.id == rowPB.id,
-        );
+
+    final isEditing = context.read<BoardBloc>().state.editingRow.isSome();
 
     return AppFlowyColumnItemCard(
       key: ValueKey(columnItem.id),
@@ -212,9 +210,6 @@ class _BoardContentState extends State<BoardContent> {
         isEditing: isEditing,
         cellBuilder: cellBuilder,
         dataController: cardController,
-        onEditEditing: (rowId) {
-          context.read<BoardBloc>().add(BoardEvent.endEditRow(rowId));
-        },
         openCard: (context) => _openCard(
           gridId,
           fieldCache,

+ 3 - 0
frontend/app_flowy/lib/plugins/board/presentation/card/board_cell.dart

@@ -0,0 +1,3 @@
+abstract class FocusableBoardCell {
+  set becomeFocus(bool isFocus);
+}

+ 35 - 6
frontend/app_flowy/lib/plugins/board/presentation/card/board_select_option_cell.dart

@@ -1,6 +1,7 @@
 import 'package:app_flowy/plugins/board/application/card/board_select_option_cell_bloc.dart';
 import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
 import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/extension.dart';
+import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
@@ -40,8 +41,9 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
         },
         builder: (context, state) {
           if (state.selectedOptions
-              .where((element) => element.id == widget.groupId)
-              .isNotEmpty) {
+                  .where((element) => element.id == widget.groupId)
+                  .isNotEmpty ||
+              state.selectedOptions.isEmpty) {
             return const SizedBox();
           } else {
             final children = state.selectedOptions
@@ -52,10 +54,17 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
                   ),
                 )
                 .toList();
-            return Align(
-              alignment: Alignment.centerLeft,
-              child: AbsorbPointer(
-                child: Wrap(children: children, spacing: 4, runSpacing: 2),
+
+            return IntrinsicHeight(
+              child: Stack(
+                alignment: AlignmentDirectional.center,
+                fit: StackFit.expand,
+                children: [
+                  Wrap(children: children, spacing: 4, runSpacing: 2),
+                  _SelectOptionDialog(
+                    controller: widget.cellControllerBuilder.build(),
+                  ),
+                ],
               ),
             );
           }
@@ -70,3 +79,23 @@ 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,
+        () {},
+      );
+    });
+  }
+}

+ 35 - 15
frontend/app_flowy/lib/plugins/board/presentation/card/board_text_cell.dart

@@ -1,15 +1,19 @@
 import 'package:app_flowy/plugins/board/application/card/board_text_cell_bloc.dart';
 import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
-import 'package:flowy_infra_ui/style_widget/text.dart';
+import 'package:app_flowy/plugins/grid/presentation/widgets/cell/cell_builder.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
 class BoardTextCell extends StatefulWidget {
   final String groupId;
+  final bool isFocus;
+
   final GridCellControllerBuilder cellControllerBuilder;
+
   const BoardTextCell({
     required this.groupId,
     required this.cellControllerBuilder,
+    this.isFocus = false,
     Key? key,
   }) : super(key: key);
 
@@ -19,14 +23,20 @@ class BoardTextCell extends StatefulWidget {
 
 class _BoardTextCellState extends State<BoardTextCell> {
   late BoardTextCellBloc _cellBloc;
+  late TextEditingController _controller;
+  SingleListenerFocusNode focusNode = SingleListenerFocusNode();
 
   @override
   void initState() {
     final cellController =
         widget.cellControllerBuilder.build() as GridCellController;
-
     _cellBloc = BoardTextCellBloc(cellController: cellController)
       ..add(const BoardTextCellEvent.initial());
+    _controller = TextEditingController(text: _cellBloc.state.content);
+
+    if (widget.isFocus) {
+      focusNode.requestFocus();
+    }
     super.initState();
   }
 
@@ -34,28 +44,38 @@ class _BoardTextCellState extends State<BoardTextCell> {
   Widget build(BuildContext context) {
     return BlocProvider.value(
       value: _cellBloc,
-      child: BlocBuilder<BoardTextCellBloc, BoardTextCellState>(
-        buildWhen: (previous, current) => previous.content != current.content,
-        builder: (context, state) {
-          if (state.content.isEmpty) {
-            return const SizedBox();
-          } else {
-            return Align(
-              alignment: Alignment.centerLeft,
-              child: ConstrainedBox(
-                constraints: const BoxConstraints(maxHeight: 120),
-                child: FlowyText.medium(state.content, fontSize: 14),
-              ),
-            );
+      child: BlocListener<BoardTextCellBloc, BoardTextCellState>(
+        listener: (context, state) {
+          if (_controller.text != state.content) {
+            _controller.text = state.content;
           }
         },
+        child: TextField(
+          controller: _controller,
+          focusNode: focusNode,
+          onChanged: (value) => focusChanged(),
+          onEditingComplete: () => focusNode.unfocus(),
+          maxLines: 1,
+          style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
+          decoration: const InputDecoration(
+            contentPadding: EdgeInsets.symmetric(vertical: 6),
+            border: InputBorder.none,
+            isDense: true,
+          ),
+        ),
       ),
     );
   }
 
+  Future<void> focusChanged() async {
+    _cellBloc.add(BoardTextCellEvent.updateText(_controller.text));
+  }
+
   @override
   Future<void> dispose() async {
     _cellBloc.close();
+    _controller.dispose();
+    focusNode.dispose();
     super.dispose();
   }
 }

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

@@ -10,8 +10,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
 import 'card_cell_builder.dart';
 import 'card_container.dart';
 
-typedef OnEndEditing = void Function(String rowId);
-
 class BoardCard extends StatefulWidget {
   final String gridId;
   final String groupId;
@@ -19,7 +17,6 @@ class BoardCard extends StatefulWidget {
   final bool isEditing;
   final CardDataController dataController;
   final BoardCellBuilder cellBuilder;
-  final OnEndEditing onEditEditing;
   final void Function(BuildContext) openCard;
 
   const BoardCard({
@@ -29,7 +26,6 @@ class BoardCard extends StatefulWidget {
     required this.isEditing,
     required this.dataController,
     required this.cellBuilder,
-    required this.onEditEditing,
     required this.openCard,
     Key? key,
   }) : super(key: key);
@@ -68,6 +64,7 @@ class _BoardCardState extends State<BoardCard> {
               widget.openCard(context);
             },
             child: Column(
+              mainAxisSize: MainAxisSize.min,
               children: _makeCells(
                 context,
                 state.cells.map((cell) => cell.identifier).toList(),
@@ -83,15 +80,33 @@ class _BoardCardState extends State<BoardCard> {
     BuildContext context,
     List<GridCellIdentifier> cells,
   ) {
-    return cells.map(
-      (GridCellIdentifier cellId) {
-        final child = widget.cellBuilder.buildCell(widget.groupId, cellId);
-        return Padding(
-          padding: const EdgeInsets.only(left: 4, right: 4, top: 6),
-          child: child,
+    final List<Widget> children = [];
+    cells.asMap().forEach(
+      (int index, GridCellIdentifier cellId) {
+        Widget child = widget.cellBuilder.buildCell(
+          widget.groupId,
+          cellId,
+          widget.isEditing,
         );
+
+        if (index != 0) {
+          child = Padding(
+            key: cellId.key(),
+            padding: const EdgeInsets.only(left: 4, right: 4, top: 8),
+            child: child,
+          );
+        } else {
+          child = Padding(
+            key: UniqueKey(),
+            padding: const EdgeInsets.only(left: 4, right: 4),
+            child: child,
+          );
+        }
+
+        children.add(child);
       },
-    ).toList();
+    );
+    return children;
   }
 
   @override

+ 6 - 1
frontend/app_flowy/lib/plugins/board/presentation/card/card_cell_builder.dart

@@ -19,7 +19,11 @@ class BoardCellBuilder {
 
   BoardCellBuilder(this.delegate);
 
-  Widget buildCell(String groupId, GridCellIdentifier cellId) {
+  Widget buildCell(
+    String groupId,
+    GridCellIdentifier cellId,
+    bool isEditing,
+  ) {
     final cellControllerBuilder = GridCellControllerBuilder(
       delegate: delegate,
       cellId: cellId,
@@ -62,6 +66,7 @@ class BoardCellBuilder {
         return BoardTextCell(
           groupId: groupId,
           cellControllerBuilder: cellControllerBuilder,
+          isFocus: isEditing,
           key: key,
         );
       case FieldType.URL:

+ 4 - 2
frontend/app_flowy/lib/plugins/grid/application/cell/cell_listener.dart

@@ -11,13 +11,15 @@ typedef UpdateFieldNotifiedValue = Either<Unit, FlowyError>;
 class CellListener {
   final String rowId;
   final String fieldId;
-  PublishNotifier<UpdateFieldNotifiedValue>? _updateCellNotifier = PublishNotifier();
+  PublishNotifier<UpdateFieldNotifiedValue>? _updateCellNotifier =
+      PublishNotifier();
   GridNotificationListener? _listener;
   CellListener({required this.rowId, required this.fieldId});
 
   void start({required void Function(UpdateFieldNotifiedValue) onCellChanged}) {
     _updateCellNotifier?.addPublishListener(onCellChanged);
-    _listener = GridNotificationListener(objectId: "$rowId:$fieldId", handler: _handler);
+    _listener = GridNotificationListener(
+        objectId: "$rowId:$fieldId", handler: _handler);
   }
 
   void _handler(GridNotification ty, Either<Uint8List, FlowyError> result) {

+ 8 - 1
frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_cache.dart

@@ -33,10 +33,17 @@ class GridCellCache {
     required this.gridId,
   });
 
-  void remove(String fieldId) {
+  void removeCellWithFieldId(String fieldId) {
     _cellDataByFieldId.remove(fieldId);
   }
 
+  void remove(GridCellCacheKey key) {
+    var map = _cellDataByFieldId[key.fieldId];
+    if (map != null) {
+      map.remove(key.rowId);
+    }
+  }
+
   void insert<T extends GridCell>(GridCellCacheKey key, T value) {
     var map = _cellDataByFieldId[key.fieldId];
     if (map == null) {

+ 1 - 1
frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/context_builder.dart

@@ -191,7 +191,7 @@ class IGridCellController<T, D> extends Equatable {
     _cellListener?.start(onCellChanged: (result) {
       result.fold(
         (_) {
-          _cellsCache.remove(fieldId);
+          _cellsCache.remove(_cacheKey);
           _loadData();
         },
         (err) => Log.error(err),

+ 2 - 1
frontend/app_flowy/lib/plugins/grid/application/row/row_cache.dart

@@ -52,7 +52,8 @@ class GridRowCache {
     //
     notifier.onRowFieldsChanged(() => _rowChangeReasonNotifier
         .receive(const RowsChangedReason.fieldDidChange()));
-    notifier.onRowFieldChanged((field) => _cellCache.remove(field.id));
+    notifier.onRowFieldChanged(
+        (field) => _cellCache.removeCellWithFieldId(field.id));
     _rowInfos = block.rows.map((rowPB) => buildGridRow(rowPB)).toList();
   }
 

+ 4 - 0
frontend/app_flowy/linux/flutter/generated_plugin_registrant.cc

@@ -7,6 +7,7 @@
 #include "generated_plugin_registrant.h"
 
 #include <flowy_infra_ui/flowy_infra_u_i_plugin.h>
+#include <hotkey_manager/hotkey_manager_plugin.h>
 #include <rich_clipboard_linux/rich_clipboard_plugin.h>
 #include <url_launcher_linux/url_launcher_plugin.h>
 #include <window_size/window_size_plugin.h>
@@ -15,6 +16,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
   g_autoptr(FlPluginRegistrar) flowy_infra_ui_registrar =
       fl_plugin_registry_get_registrar_for_plugin(registry, "FlowyInfraUIPlugin");
   flowy_infra_u_i_plugin_register_with_registrar(flowy_infra_ui_registrar);
+  g_autoptr(FlPluginRegistrar) hotkey_manager_registrar =
+      fl_plugin_registry_get_registrar_for_plugin(registry, "HotkeyManagerPlugin");
+  hotkey_manager_plugin_register_with_registrar(hotkey_manager_registrar);
   g_autoptr(FlPluginRegistrar) rich_clipboard_linux_registrar =
       fl_plugin_registry_get_registrar_for_plugin(registry, "RichClipboardPlugin");
   rich_clipboard_plugin_register_with_registrar(rich_clipboard_linux_registrar);

+ 1 - 0
frontend/app_flowy/linux/flutter/generated_plugins.cmake

@@ -4,6 +4,7 @@
 
 list(APPEND FLUTTER_PLUGIN_LIST
   flowy_infra_ui
+  hotkey_manager
   rich_clipboard_linux
   url_launcher_linux
   window_size

+ 2 - 0
frontend/app_flowy/macos/Flutter/GeneratedPluginRegistrant.swift

@@ -9,6 +9,7 @@ import connectivity_plus_macos
 import device_info_plus_macos
 import flowy_infra_ui
 import flowy_sdk
+import hotkey_manager
 import package_info_plus_macos
 import path_provider_macos
 import rich_clipboard_macos
@@ -21,6 +22,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
   DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
   FlowyInfraUIPlugin.register(with: registry.registrar(forPlugin: "FlowyInfraUIPlugin"))
   FlowySdkPlugin.register(with: registry.registrar(forPlugin: "FlowySdkPlugin"))
+  HotkeyManagerPlugin.register(with: registry.registrar(forPlugin: "HotkeyManagerPlugin"))
   FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
   PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
   RichClipboardPlugin.register(with: registry.registrar(forPlugin: "RichClipboardPlugin"))

+ 4 - 0
frontend/app_flowy/packages/appflowy_board/CHANGELOG.md

@@ -1,3 +1,7 @@
+# 0.0.6
+* Support scroll to bottom
+* Fix some bugs
+
 # 0.0.5
 * Optimize insert card animation
 * Enable insert card at the end of the column

+ 48 - 25
frontend/app_flowy/packages/appflowy_board/example/lib/multi_board_list_example.dart

@@ -11,13 +11,13 @@ class MultiBoardListExample extends StatefulWidget {
 class _MultiBoardListExampleState extends State<MultiBoardListExample> {
   final AFBoardDataController boardDataController = AFBoardDataController(
     onMoveColumn: (fromColumnId, fromIndex, toColumnId, toIndex) {
-      debugPrint('Move column from $fromIndex to $toIndex');
+      // debugPrint('Move column from $fromIndex to $toIndex');
     },
     onMoveColumnItem: (columnId, fromIndex, toIndex) {
-      debugPrint('Move $columnId:$fromIndex to $columnId:$toIndex');
+      // debugPrint('Move $columnId:$fromIndex to $columnId:$toIndex');
     },
     onMoveColumnItemToColumn: (fromColumnId, fromIndex, toColumnId, toIndex) {
-      debugPrint('Move $fromColumnId:$fromIndex to $toColumnId:$toIndex');
+      // debugPrint('Move $fromColumnId:$fromIndex to $toColumnId:$toIndex');
     },
   );
 
@@ -96,7 +96,7 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
           },
           cardBuilder: (context, column, columnItem) {
             return AppFlowyColumnItemCard(
-              key: ObjectKey(columnItem),
+              key: ValueKey(columnItem.id),
               child: _buildCard(columnItem),
             );
           },
@@ -121,33 +121,56 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
     }
 
     if (item is RichTextItem) {
-      return Align(
-        alignment: Alignment.centerLeft,
-        child: Padding(
-          padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20),
-          child: Column(
-            crossAxisAlignment: CrossAxisAlignment.start,
-            children: [
-              Text(
-                item.title,
-                style: const TextStyle(fontSize: 14),
-                textAlign: TextAlign.left,
-              ),
-              const SizedBox(height: 10),
-              Text(
-                item.subtitle,
-                style: const TextStyle(fontSize: 12, color: Colors.grey),
-              )
-            ],
-          ),
-        ),
-      );
+      return RichTextCard(item: item);
     }
 
     throw UnimplementedError();
   }
 }
 
+class RichTextCard extends StatefulWidget {
+  final RichTextItem item;
+  const RichTextCard({
+    required this.item,
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  State<RichTextCard> createState() => _RichTextCardState();
+}
+
+class _RichTextCardState extends State<RichTextCard> {
+  @override
+  void initState() {
+    super.initState();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Align(
+      alignment: Alignment.centerLeft,
+      child: Padding(
+        padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20),
+        child: Column(
+          crossAxisAlignment: CrossAxisAlignment.start,
+          children: [
+            Text(
+              widget.item.title,
+              style: const TextStyle(fontSize: 14),
+              textAlign: TextAlign.left,
+            ),
+            const SizedBox(height: 10),
+            Text(
+              widget.item.subtitle,
+              style: const TextStyle(fontSize: 12, color: Colors.grey),
+            )
+          ],
+        ),
+      ),
+    );
+  }
+}
+
 class TextItem extends AFColumnItem {
   final String s;
 

+ 2 - 2
frontend/app_flowy/packages/appflowy_board/lib/src/utils/log.dart

@@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
 const DART_LOG = "Dart_LOG";
 
 class Log {
-  static const enableLog = false;
+  static const enableLog = true;
 
   static void info(String? message) {
     if (enableLog) {
@@ -26,7 +26,7 @@ class Log {
 
   static void trace(String? message) {
     if (enableLog) {
-      debugPrint('❗️[Trace] - ${DateTime.now().second}=> $message');
+      // debugPrint('❗️[Trace] - ${DateTime.now().second}=> $message');
     }
   }
 }

+ 9 - 4
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board.dart

@@ -64,7 +64,7 @@ class AFBoard extends StatelessWidget {
   final BoxConstraints columnConstraints;
 
   ///
-  final BoardPhantomController phantomController;
+  late final BoardPhantomController phantomController;
 
   final ScrollController? scrollController;
 
@@ -85,8 +85,12 @@ class AFBoard extends StatelessWidget {
     this.columnConstraints = const BoxConstraints(maxWidth: 200),
     this.config = const AFBoardConfig(),
     Key? key,
-  })  : phantomController = BoardPhantomController(delegate: dataController),
-        super(key: key);
+  }) : super(key: key) {
+    phantomController = BoardPhantomController(
+      delegate: dataController,
+      columnsState: _columnState,
+    );
+  }
 
   @override
   Widget build(BuildContext context) {
@@ -194,6 +198,7 @@ class _AFBoardContentState extends State<AFBoardContent> {
           dataSource: widget.dataController,
           direction: Axis.horizontal,
           interceptor: interceptor,
+          reorderable: false,
           children: _buildColumns(),
         );
 
@@ -244,7 +249,7 @@ class _AFBoardContentState extends State<AFBoardContent> {
           child: Consumer<AFBoardColumnDataController>(
             builder: (context, value, child) {
               final boardColumn = AFBoardColumnWidget(
-                key: PageStorageKey<String>(columnData.id),
+                // key: PageStorageKey<String>(columnData.id),
                 // key: GlobalObjectKey(columnData.id),
                 margin: _marginFromIndex(columnIndex),
                 itemMargin: widget.config.columnItemPadding,

+ 2 - 2
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column.dart

@@ -92,7 +92,7 @@ class AFBoardColumnWidget extends StatefulWidget {
 
   final ReorderDragTargetIndexKeyStorage? dragTargetIndexKeyStorage;
 
-  final GlobalKey globalKey;
+  final GlobalObjectKey globalKey;
 
   AFBoardColumnWidget({
     Key? key,
@@ -111,7 +111,7 @@ class AFBoardColumnWidget extends StatefulWidget {
     this.itemMargin = EdgeInsets.zero,
     this.cornerRadius = 0.0,
     this.backgroundColor = Colors.transparent,
-  })  : globalKey = GlobalKey(),
+  })  : globalKey = GlobalObjectKey(dataSource.columnData.id),
         config = const ReorderFlexConfig(),
         super(key: key);
 

+ 5 - 0
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target.dart

@@ -75,6 +75,7 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
   final AnimationController deleteAnimationController;
 
   final bool useMoveAnimation;
+  final bool draggable;
 
   const ReorderDragTarget({
     Key? key,
@@ -88,6 +89,7 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
     required this.insertAnimationController,
     required this.deleteAnimationController,
     required this.useMoveAnimation,
+    required this.draggable,
     this.onAccept,
     this.onLeave,
     this.draggableTargetBuilder,
@@ -132,6 +134,9 @@ class _ReorderDragTargetState<T extends DragTargetData>
     List<T?> acceptedCandidates,
     List<dynamic> rejectedCandidates,
   ) {
+    if (!widget.draggable) {
+      return widget.child;
+    }
     Widget feedbackBuilder = Builder(builder: (BuildContext context) {
       BoxConstraints contentSizeConstraints =
           BoxConstraints.loose(_draggingFeedbackSize!);

+ 2 - 1
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target_interceptor.dart

@@ -131,6 +131,7 @@ class CrossReorderFlexDragTargetInterceptor extends DragTargetInterceptor {
   final String reorderFlexId;
   final List<String> acceptedReorderFlexIds;
   final CrossReorderFlexDragTargetDelegate delegate;
+
   @override
   final ReorderFlexDraggableTargetBuilder? draggableTargetBuilder;
 
@@ -188,7 +189,7 @@ class CrossReorderFlexDragTargetInterceptor extends DragTargetInterceptor {
     );
 
     Log.debug(
-        '[$CrossReorderFlexDragTargetInterceptor] dargTargetIndex: $dragTargetIndex, reorderFlexId: $reorderFlexId');
+        '[$CrossReorderFlexDragTargetInterceptor] isNewDragTarget: $isNewDragTarget, dargTargetIndex: $dragTargetIndex, reorderFlexId: $reorderFlexId');
 
     if (isNewDragTarget == false) {
       delegate.updateDragTargetData(reorderFlexId, dragTargetIndex);

+ 5 - 2
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart

@@ -82,6 +82,8 @@ class ReorderFlex extends StatefulWidget {
 
   final ReorderDragTargetIndexKeyStorage? dragTargetIndexKeyStorage;
 
+  final bool reorderable;
+
   ReorderFlex({
     Key? key,
     this.scrollController,
@@ -89,6 +91,7 @@ class ReorderFlex extends StatefulWidget {
     required this.children,
     required this.config,
     required this.onReorder,
+    this.reorderable = true,
     this.dragStateStorage,
     this.dragTargetIndexKeyStorage,
     this.onDragStarted,
@@ -378,14 +381,13 @@ class ReorderFlexState extends State<ReorderFlex>
               dragState.currentIndex,
             );
           }
-
           dragState.endDragging();
           widget.onDragEnded?.call();
         });
       },
       onWillAccept: (FlexDragTargetData dragTargetData) {
         // Do not receive any events if the Insert item is animating.
-        if (_animation.deleteController.isAnimating) {
+        if (_animation.insertController.isAnimating) {
           return false;
         }
 
@@ -421,6 +423,7 @@ class ReorderFlexState extends State<ReorderFlex>
       deleteAnimationController: _animation.deleteController,
       draggableTargetBuilder: widget.interceptor?.draggableTargetBuilder,
       useMoveAnimation: widget.config.useMoveAnimation,
+      draggable: widget.reorderable,
       child: child,
     );
   }

+ 20 - 16
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_controller.dart

@@ -1,7 +1,7 @@
+import 'package:appflowy_board/appflowy_board.dart';
 import 'package:flutter/widgets.dart';
 
 import '../../utils/log.dart';
-import '../board_column/board_column_data.dart';
 import '../reorder_flex/drag_state.dart';
 import '../reorder_flex/drag_target.dart';
 import '../reorder_flex/drag_target_interceptor.dart';
@@ -39,8 +39,12 @@ class BoardPhantomController extends OverlapDragTargetDelegate
     with CrossReorderFlexDragTargetDelegate {
   PhantomRecord? phantomRecord;
   final BoardPhantomControllerDelegate delegate;
-  final columnsState = ColumnPhantomStateController();
-  BoardPhantomController({required this.delegate});
+  final BoardColumnsState columnsState;
+  final phantomState = ColumnPhantomState();
+  BoardPhantomController({
+    required this.delegate,
+    required this.columnsState,
+  });
 
   bool isFromColumn(String columnId) {
     if (phantomRecord != null) {
@@ -59,19 +63,19 @@ class BoardPhantomController extends OverlapDragTargetDelegate
   }
 
   void columnStartDragging(String columnId) {
-    columnsState.setColumnIsDragging(columnId, true);
+    phantomState.setColumnIsDragging(columnId, true);
   }
 
   /// Remove the phantom in the column when the column is end dragging.
   void columnEndDragging(String columnId) {
-    columnsState.setColumnIsDragging(columnId, false);
+    phantomState.setColumnIsDragging(columnId, false);
 
     if (phantomRecord == null) return;
 
     final fromColumnId = phantomRecord!.fromColumnId;
     final toColumnId = phantomRecord!.toColumnId;
     if (fromColumnId == columnId) {
-      columnsState.notifyDidRemovePhantom(toColumnId);
+      phantomState.notifyDidRemovePhantom(toColumnId);
     }
 
     if (phantomRecord!.toColumnId == columnId) {
@@ -82,8 +86,8 @@ class BoardPhantomController extends OverlapDragTargetDelegate
         phantomRecord!.toColumnIndex,
       );
 
-      Log.debug(
-          "[$BoardPhantomController] did move ${phantomRecord.toString()}");
+      // Log.debug(
+      //     "[$BoardPhantomController] did move ${phantomRecord.toString()}");
       phantomRecord = null;
     }
   }
@@ -91,8 +95,8 @@ class BoardPhantomController extends OverlapDragTargetDelegate
   /// Remove the phantom in the column if it contains phantom
   void _removePhantom(String columnId) {
     if (delegate.removePhantom(columnId)) {
-      columnsState.notifyDidRemovePhantom(columnId);
-      columnsState.removeColumnListener(columnId);
+      phantomState.notifyDidRemovePhantom(columnId);
+      phantomState.removeColumnListener(columnId);
     }
   }
 
@@ -105,7 +109,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate
       index: phantomIndex,
       dragTargetData: dragTargetData,
     );
-    columnsState.addColumnListener(toColumnId, phantomContext);
+    phantomState.addColumnListener(toColumnId, phantomContext);
 
     delegate.insertPhantom(
       toColumnId,
@@ -113,7 +117,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate
       PhantomColumnItem(phantomContext),
     );
 
-    columnsState.notifyDidInsertPhantom(toColumnId, phantomIndex);
+    phantomState.notifyDidInsertPhantom(toColumnId, phantomIndex);
   }
 
   /// Reset or initial the [PhantomRecord]
@@ -150,7 +154,8 @@ class BoardPhantomController extends OverlapDragTargetDelegate
     if (phantomRecord == null) {
       _resetPhantomRecord(reorderFlexId, dragTargetData, dragTargetIndex);
       _insertPhantom(reorderFlexId, dragTargetData, dragTargetIndex);
-      return false;
+
+      return true;
     }
 
     final isNewDragTarget = phantomRecord!.toColumnId != reorderFlexId;
@@ -204,7 +209,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate
 
   @override
   int getInsertedIndex(String dragTargetId) {
-    if (columnsState.isDragging(dragTargetId)) {
+    if (phantomState.isDragging(dragTargetId)) {
       return -1;
     }
 
@@ -243,8 +248,7 @@ class PhantomRecord {
     if (fromColumnIndex == index) {
       return;
     }
-    Log.debug(
-        '[$PhantomRecord] Update Column:[$fromColumnId] remove position to $index');
+
     fromColumnIndex = index;
   }
 

+ 1 - 1
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_state.dart

@@ -1,7 +1,7 @@
 import 'phantom_controller.dart';
 import 'package:flutter/material.dart';
 
-class ColumnPhantomStateController {
+class ColumnPhantomState {
   final _states = <String, ColumnState>{};
 
   void setColumnIsDragging(String columnId, bool isDragging) {

+ 1 - 1
frontend/app_flowy/packages/appflowy_board/pubspec.yaml

@@ -1,6 +1,6 @@
 name: appflowy_board
 description: AppFlowy board implementation.
-version: 0.0.5
+version: 0.0.6
 homepage: https://github.com/AppFlowy-IO/AppFlowy
 repository: https://github.com/AppFlowy-IO/AppFlowy/tree/main/frontend/app_flowy/packages/appflowy_board
 

+ 13 - 2
frontend/rust-lib/flowy-grid/src/entities/block_entities.rs

@@ -120,17 +120,28 @@ pub struct InsertedRowPB {
 
     #[pb(index = 2, one_of)]
     pub index: Option<i32>,
+
+    #[pb(index = 3)]
+    pub is_new: bool,
 }
 
 impl InsertedRowPB {
     pub fn new(row: RowPB) -> Self {
-        Self { row, index: None }
+        Self {
+            row,
+            index: None,
+            is_new: false,
+        }
     }
 }
 
 impl std::convert::From<RowPB> for InsertedRowPB {
     fn from(row: RowPB) -> Self {
-        Self { row, index: None }
+        Self {
+            row,
+            index: None,
+            is_new: false,
+        }
     }
 }
 

+ 1 - 0
frontend/rust-lib/flowy-grid/src/services/block_manager.rs

@@ -164,6 +164,7 @@ impl GridBlockManager {
         let insert_row = InsertedRowPB {
             index: Some(to as i32),
             row: make_row_from_row_rev(row_rev),
+            is_new: false,
         };
 
         let notified_changeset = GridBlockChangesetPB {

+ 1 - 0
frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs

@@ -98,6 +98,7 @@ impl GridViewRevisionEditor {
                 let inserted_row = InsertedRowPB {
                     row: row_pb.clone(),
                     index: None,
+                    is_new: true,
                 };
                 let changeset = GroupChangesetPB::insert(group_id.clone(), vec![inserted_row]);
                 self.notify_did_update_group(changeset).await;