Skip to content

Conversation

PirateBrook
Copy link

@PirateBrook PirateBrook commented Jun 3, 2025

related issue #5524 [FR] Board move multiple items to a group with shortcut

Feature Preview

Kapture.2025-06-03.at.15.43.14.mp4

PR Checklist

  • My code adheres to AppFlowy's Conventions
  • I've listed at least one issue that this PR fixes in the description above.
  • I've added a test(s) to validate changes in this PR, or this PR only contains semantic changes.
  • All existing tests are passing.

Summary by Sourcery

Enable batch moving of board items by extending the moveGroupRow flow end-to-end—from RPC payloads and Rust handlers to Dart events, controller, and UI bindings—so multiple cards can be moved together via shortcut.

New Features:

  • Support moving multiple selected board cards to a target group or position at once
  • Add a keyboard shortcut to move multiple selected cards to the previous or next group

Enhancements:

  • Refactor moveGroupRow RPC payloads, params, and handlers to accept lists of row IDs
  • Update BoardBloc, DatabaseController, and event definitions to handle batch moves of GroupedRowId lists
  • Improve adjacent-group move logic to compute and move multiple rows across groups

Copy link
Contributor

sourcery-ai bot commented Jun 3, 2025

Reviewer's Guide

This PR extends the board’s move operations to handle multiple items at once via keyboard shortcuts by generalizing the moveGroupRow API across Flutter and Rust layers, enhancing the BoardBloc logic for bulk adjacent‐group moves, and adding necessary utility support.

Sequence Diagram for Moving Multiple Items to an Adjacent Group

sequenceDiagram
    actor User
    participant BoardShortcutContainer
    participant BoardBloc
    participant DatabaseController
    participant DatabaseViewBackendService
    participant RustBackend

    User->>BoardShortcutContainer: Presses shortcut to move multiple items
    BoardShortcutContainer->>BoardBloc: add(BoardEvent.moveGroupToAdjacentGroup(List<GroupedRowId>, toPrevious))
    BoardBloc->>BoardBloc: Identify target group, filter row IDs for moving
    BoardBloc->>DatabaseController: rowCache.getRows(List<RowId>)
    DatabaseController-->>BoardBloc: return List<RowInfo> (with RowMetaPB)
    BoardBloc->>DatabaseController: moveGroupRow(fromRow: List<RowMetaPB>, fromGroupId, toGroupId)
    DatabaseController->>DatabaseViewBackendService: moveGroupRow(fromRowIds: List<RowId>, fromGroupId, toGroupId)
    DatabaseViewBackendService->>RustBackend: Send MoveGroupRowPayloadPB (with from_row_ids: List<String>)
    RustBackend->>RustBackend: Loop: For each rowId in from_row_ids, call database_editor.move_group_row(...)
    RustBackend-->>DatabaseViewBackendService: Result
    DatabaseViewBackendService-->>DatabaseController: Result
    DatabaseController-->>BoardBloc: Result
    alt On Success
        BoardBloc->>BoardBloc: emit(BoardState.setFocus(List<GroupedRowId>))
    end
Loading

Class Diagram for Board Event and Bloc Logic for Multi-Item Move

classDiagram
  class BoardEvent {
    <<factory>> +moveGroupToAdjacentGroup(List~GroupedRowId~ groupedRowIds, bool toPrevious)
  }
  class _MoveGroupToAdjacentGroup {
    +List~GroupedRowId~ groupedRowIds -- Parameter type changed from GroupedRowId --
    +bool toPrevious
  }
  BoardEvent <|-- _MoveGroupToAdjacentGroup

  class BoardBloc {
    +on<_MoveGroupToAdjacentGroup>(_handleMoveGroupToAdjacentGroup)
    -_handleMoveGroupToAdjacentGroup(event: _MoveGroupToAdjacentGroup, emit)
    %% Methods called within handler reflect new list-based logic %%
    #databaseController.moveGroupRow(fromRow: List~RowMetaPB~, ...)
    #databaseController.rowCache.getRows(List~RowId~)
  }
  class GroupedRowId {
    +String groupId
    +String rowId
  }
  BoardBloc *-- DatabaseController : uses
Loading

Class Diagram for DatabaseController and Service Layer Changes

classDiagram
    class DatabaseController {
        - _databaseViewBackendSvc: DatabaseViewBackendService
        + moveGroupRow(fromRow: List~RowMetaPB~, fromGroupId: String, toGroupId: String, toRow: RowMetaPB?): Future~FlowyResult~ 
          -- Parameter fromRow changed from RowMetaPB to List<RowMetaPB> --
    }
    class DatabaseViewBackendService {
        + moveGroupRow(fromRowIds: List~RowId~, fromGroupId: String, toGroupId: String, toRowId: RowId?): Future~FlowyResult~
          -- Parameter fromRowIds changed from RowId to List<RowId> --
    }
    class RowMetaPB {
      +String id
      %% other fields %%
    }
    class RowId~String~
    DatabaseController o-- DatabaseViewBackendService : uses
    DatabaseController ..> RowMetaPB : uses
    DatabaseViewBackendService ..> RowId : uses
Loading

File-Level Changes

Change Details Files
Generalize moveGroupRow API to support multiple rows
  • Updated MoveGroupRowPayloadPB to accept a List of from_row_ids
  • Modified DatabaseViewBackendService and DatabaseController to pass and map List
  • Refactored UI call sites (BoardBloc, BoardPage, BoardShortcutContainer) to wrap single rows into lists
frontend/appflowy_flutter/lib/plugins/database/domain/database_view_service.dart
frontend/appflowy_flutter/lib/plugins/database/application/database_controller.dart
frontend/appflowy_flutter/lib/plugins/database/board/application/board_bloc.dart
frontend/appflowy_flutter/lib/plugins/database/board/presentation/board_page.dart
frontend/appflowy_flutter/lib/plugins/database/board/presentation/widgets/board_shortcut_container.dart
Enhance BoardEvent and BoardBloc for bulk adjacent‐group moves
  • Changed moveGroupToAdjacentGroup event to accept List
  • Implemented multi-selection index calculation to derive from/to group indices
  • Extracted rowIds and batch-called moveGroupRow, then updated focus for all moved items
frontend/appflowy_flutter/lib/plugins/database/board/application/board_bloc.dart
frontend/appflowy_flutter/lib/plugins/database/board/application/board_event.dart
Support bulk moves in Rust backend event handler
  • Loop over params.from_row_ids in move_group_row_handler to move each row
  • Updated MoveGroupRowPayloadPB and MoveGroupRowParams to use Vec/Vec
  • Adjusted TryInto implementation to collect from_row_ids vector
frontend/rust-lib/flowy-database2/src/event_handler.rs
frontend/rust-lib/flowy-database2/src/entities/database_entities.rs
Add multi-row fetch utility in cache
  • Introduced getRows method in RowCache to retrieve multiple RowInfos by IDs
frontend/appflowy_flutter/lib/plugins/database/application/row/row_cache.dart

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@CLAassistant
Copy link

CLAassistant commented Jun 3, 2025

CLA assistant check
All committers have signed the CLA.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @PirateBrook - I've reviewed your changes and found some issues that need to be addressed.

Blocking issues:

  • _moveGroupToAdjacentGroup no longer returns a bool consistently (link)

General comments:

  • In Dart, checkedGroupIndexs.sorted((a, b) => a - b) on a Set isn’t a built-in method—convert the Set to a List and call sort() (or import the collection extension) to avoid runtime errors.
  • The Rust handler loops and calls move_group_row per ID, which may hurt performance or transactional consistency; consider adding a batch move operation in your database editor instead of issuing individual calls.
Here's what I looked at during the review
  • 🔴 General issues: 1 blocking issue, 1 other issue
  • 🟢 Security: all looks good
  • 🟢 Testing: all looks good
  • 🟢 Complexity: all looks good
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

toPrevious ? currentGroupIndex - 1 : currentGroupIndex + 1;
if (fromRow != null &&
moveGroupToAdjacentGroup: (groupedRowIds, toPrevious) async {
final checkedGroupIndexs = groupedRowIds
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick (typo): Typo in variable name 'checkedGroupIndexs'

Rename to checkedGroupIndices or checkedGroupIndexes for correct pluralization and clarity.

}

bool _moveGroupToAdjacentGroup(BuildContext context, bool toPrevious) {
if (focusScope.value.length != 1) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): _moveGroupToAdjacentGroup no longer returns a bool consistently

Add an explicit return true after dispatching, and ensure return false is used for invalid cases, to match the method's bool return type.

@LucasXu0 LucasXu0 added the v0.9.5 label Jun 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants