Skip to content

Conversation

samruddhi-Rahegaonkar
Copy link
Member

@samruddhi-Rahegaonkar samruddhi-Rahegaonkar commented Jul 20, 2025

Fixes #1377

Changes

  • Added support for undo/redo functionality for draw clipart
  • User may able to undo/ redo there work

Screenshots / Recordings

Checklist:

  • No hard coding: I have used resources from constants.dart without hard coding any value.
  • No end of file edits: No modifications done at end of resource files.
  • Code reformatting: I have reformatted code and fixed indentation in every file included in this pull request.
  • Code analyzation: My code passes analyzations run in flutter analyze and tests run in flutter test.

Summary by Sourcery

Add undo/redo and shape-drawing support to the clipart editor and refactor UI and provider for improved state management

New Features:

  • Add Undo and Redo buttons to the drawing toolbar
  • Introduce toggleable shape modes (freehand, square, rectangle, circle, triangle) with live preview
  • Persist drawing state at pan start to enable reversible edits

Enhancements:

  • Refactor DrawBadge screen into modular widget builders and streamline layout
  • Simplify canvas gesture handling in BMBadge using onPanStart/Update/End
  • Extend DrawBadgeProvider with preview grid merging, undo/redo stacks, and grid position utilities

Copy link
Contributor

sourcery-ai bot commented Jul 20, 2025

Reviewer's Guide

This PR implements full undo/redo support and toggleable shape‐drawing modes in the draw clipart feature, refactors the drawing toolbar UI into modular builder methods, and enhances gesture handling and grid management in the provider.

Sequence diagram for drawing with undo/redo and shape preview

sequenceDiagram
    actor User
    participant DrawBadge as DrawBadgeScreen
    participant BMBadge
    participant Provider as DrawBadgeProvider

    User->>DrawBadge: Tap and drag to draw
    DrawBadge->>BMBadge: Pass gesture events
    BMBadge->>Provider: pushToUndoStack()
    BMBadge->>Provider: setCell()/clearPreviewGrid() (for shape preview)
    BMBadge->>Provider: commitGridUpdate() (on gesture end)
    User->>DrawBadge: Tap Undo
    DrawBadge->>Provider: undo()
    User->>DrawBadge: Tap Redo
    DrawBadge->>Provider: redo()
Loading

Sequence diagram for toggling and using shape tools

sequenceDiagram
    actor User
    participant DrawBadge as DrawBadgeScreen
    participant Provider as DrawBadgeProvider

    User->>DrawBadge: Tap Shapes button
    DrawBadge->>DrawBadge: Show shape options
    User->>DrawBadge: Select shape (e.g., Rectangle)
    DrawBadge->>Provider: setShape(Rectangle)
    User->>DrawBadge: Draw gesture
    DrawBadge->>Provider: setCell()/clearPreviewGrid() (preview shape)
    User->>DrawBadge: Release gesture
    DrawBadge->>Provider: commitGridUpdate()
Loading

File-Level Changes

Change Details Files
Add undo/redo history and controls
  • Maintain _undoStack and _redoStack in DrawBadgeProvider
  • Push current grid to undoStack on pan start and reset/commit
  • Implement undo() and redo() methods to swap grid states
  • Expose canUndo/canRedo getters
  • Add Undo and Redo buttons in draw_badge_screen with setState wrappers
lib/providers/draw_badge_provider.dart
lib/view/draw_badge_screen.dart
Introduce shape drawing modes with preview and commit flow
  • Define DrawShape enum and track selectedShape in provider
  • Use a separate preview grid and merge it in getDrawViewGrid()
  • Switch drawing logic in _handlePanUpdate to render freehand, square, rectangle, circle, triangle previews
  • Commit preview grid to permanent grid in _handlePanEnd and clear previews
  • Toggle shape options panel and reset to freehand when closed
lib/providers/draw_badge_provider.dart
lib/virtualbadge/view/draw_badge.dart
lib/view/draw_badge_screen.dart
Refactor draw toolbar UI into modular builder methods
  • Replace inline build logic with _buildDrawEraseButton, _buildResetButton, _buildSaveButton, _buildShapesToggleButton, _buildUndoButton, _buildRedoButton
  • Wrap body in SingleChildScrollView, ConstrainedBox, and IntrinsicHeight for responsive layout
  • Show/hide shape options row based on _showShapeOptions flag
lib/view/draw_badge_screen.dart
Enhance gesture handling and grid coordinate mapping
  • Add onPanStart and onPanEnd callbacks alongside onPanUpdate
  • Compute local tap position via context.findRenderObject
  • Map global touches to grid indices using getGridPosition() and cellSize
  • Push to undoStack at pan start
lib/virtualbadge/view/draw_badge.dart
Improve provider grid management and initialization
  • Merge preview and permanent grids in getDrawViewGrid()
  • Implement setCell() with preview flag and notifyListeners() only for permanent changes
  • Add clearPreviewGrid(), commitGridUpdate(), and _copyGrid() utilities
  • Refactor updateDrawViewGrid() and resetDrawViewGrid() to push to undo stack
lib/providers/draw_badge_provider.dart

Assessment against linked issues

Issue Objective Addressed Explanation
#1377 Implement undo and redo functionality in the Draw Clipart screen for freehand and shape drawing.

Possibly linked issues


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

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 @samruddhi-Rahegaonkar - I've reviewed your changes - here's some feedback:

  • The Undo and Redo buttons are always enabled—consider using drawProvider.canUndo/canRedo to disable them when no actions are available.
  • The new UndoRedoControls widget isn’t actually integrated anywhere—either wire it into your screen or remove it to avoid dead code.
  • Consider renaming GridPosition.x and .y to row/col (or similar) to make the mapping between coordinates and grid indices clearer.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The Undo and Redo buttons are always enabled—consider using drawProvider.canUndo/canRedo to disable them when no actions are available.
- The new UndoRedoControls widget isn’t actually integrated anywhere—either wire it into your screen or remove it to avoid dead code.
- Consider renaming GridPosition.x and .y to row/col (or similar) to make the mapping between coordinates and grid indices clearer.

## Individual Comments

### Comment 1
<location> `lib/virtualbadge/view/draw_badge.dart:45` </location>
<code_context>
+    final localPosition = _getLocalPosition(details.globalPosition);
+    final shape = drawProvider.selectedShape;
+
+    final start = drawProvider.getGridPosition(dragStart!, _cellSize);
+    final end = drawProvider.getGridPosition(localPosition, _cellSize);
+
</code_context>

<issue_to_address>
Potential null dereference of dragStart.

Since dragStart is force-unwrapped, add a null check or assertion to avoid runtime errors if _handlePanUpdate is called before _handlePanStart.
</issue_to_address>

### Comment 2
<location> `lib/providers/draw_badge_provider.dart:73` </location>
<code_context>
+    }
+  }
+
+  void commitGridUpdate() {
+    _pushToUndoStack();
+    for (int i = 0; i < rows; i++) {
</code_context>

<issue_to_address>
commitGridUpdate overwrites cells with preview values without considering erasing.

The current logic only updates cells when _previewGrid[i][j] is true, so erasing (when a preview cell is false) isn't handled. Please update the logic to support both drawing and erasing in preview mode.
</issue_to_address>

### Comment 3
<location> `lib/view/draw_badge_screen.dart:197` </location>
<code_context>
+        fileHelper.generateClipartCache();
+        ToastUtils().showToast("Clipart Saved Successfully");
+
+        Future.delayed(const Duration(milliseconds: 800), () {
+          Navigator.of(context).popUntil((route) => route.isFirst);
+        });
</code_context>

<issue_to_address>
Fixed delay before navigation may not be robust.

Trigger navigation after the save operation completes, rather than relying on a fixed delay, to ensure reliability regardless of save duration.

Suggested implementation:

```
        await fileHelper.generateClipartCache();
        ToastUtils().showToast("Clipart Saved Successfully");
        if (mounted) {
          Navigator.of(context).popUntil((route) => route.isFirst);
        }



  @override

```

- If `generateClipartCache()` is not already `async` and does not return a `Future`, you will need to make it asynchronous for this to work.
- The containing function must be marked as `async` and return a `Future`, and any callers must handle this.
- If the save operation itself is asynchronous, you should also `await` it before generating the cache.
</issue_to_address>

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.

@samruddhi-Rahegaonkar samruddhi-Rahegaonkar marked this pull request as draft July 20, 2025 05:13
@samruddhi-Rahegaonkar samruddhi-Rahegaonkar marked this pull request as ready for review July 20, 2025 13:58
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 @samruddhi-Rahegaonkar - I've reviewed your changes - here's some feedback:

  • Consider disabling or visually dimming the Undo/Redo buttons when canUndo/canRedo is false so users get clear feedback about available actions.
  • You might want to cap the size of the undo/redo stacks (e.g. a max history length) to prevent unbounded memory growth during long drawing sessions.
  • The toolbar has grown quite large—extracting it (and its button builders) into its own widget or smaller components could improve readability and maintenance.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Consider disabling or visually dimming the Undo/Redo buttons when canUndo/canRedo is false so users get clear feedback about available actions.
- You might want to cap the size of the undo/redo stacks (e.g. a max history length) to prevent unbounded memory growth during long drawing sessions.
- The toolbar has grown quite large—extracting it (and its button builders) into its own widget or smaller components could improve readability and maintenance.

## Individual Comments

### Comment 1
<location> `lib/providers/draw_badge_provider.dart:54` </location>
<code_context>
-          _drawViewGrid[i][j] = badgeData[i][j];
-        } else {
-          _drawViewGrid[i][j] = false;
+  void setCell(int row, int col, bool value, {bool preview = false}) {
+    if (row >= 0 && row < rows && col >= 0 && col < cols) {
+      if (preview) {
</code_context>

<issue_to_address>
setCell notifies listeners on every cell change, which may cause excessive rebuilds.

Batch updates and call notifyListeners() once after all relevant cells are set to avoid multiple unnecessary UI rebuilds during bulk operations.

Suggested implementation:

```
  void setCell(int row, int col, bool value, {bool preview = false, bool notify = true}) {
    if (row >= 0 && row < rows && col >= 0 && col < cols) {
      if (preview) {
        _previewGrid[row][col] = value;
      } else {
        _drawViewGrid[row][col] = value;
        if (notify) {
          notifyListeners();
        }
      }
    }
  }

  /// Batch update cells and notify listeners only once after all updates.
  void setCells(List<Tuple4<int, int, bool, bool>> updates) {
    for (final update in updates) {
      // update.item1: row, update.item2: col, update.item3: value, update.item4: preview
      setCell(update.item1, update.item2, update.item3, preview: update.item4, notify: false);
    }
    notifyListeners();
  }

```

- You will need to import a tuple class, such as `package:tuple/tuple.dart`, or define your own data structure for the batch update parameter in `setCells`.
- Update any code that performs bulk cell updates to use `setCells` instead of calling `setCell` in a loop.
- If you want a more type-safe or readable batch update, consider defining a small class or struct for the cell update instead of using `Tuple4`.
</issue_to_address>

### Comment 2
<location> `lib/providers/draw_badge_provider.dart:73` </location>
<code_context>
+    }
+  }
+
+  void commitGridUpdate() {
+    _pushToUndoStack();
+    for (int i = 0; i < rows; i++) {
</code_context>

<issue_to_address>
commitGridUpdate overwrites cells with preview values, potentially losing previous cell state.

Consider merging _previewGrid and _drawViewGrid using logical OR/AND or a more explicit strategy to avoid unintentionally overwriting existing cell states.
</issue_to_address>

### Comment 3
<location> `lib/providers/draw_badge_provider.dart:112` </location>
<code_context>
+
+  // ========== UNDO / REDO ==========
+
+  void _pushToUndoStack() {
+    _undoStack.add(_copyGrid(_drawViewGrid));
+    _redoStack.clear(); // Invalidate redo stack on new action
</code_context>

<issue_to_address>
Undo/redo stacks may grow unbounded, leading to increased memory usage.

Consider setting a maximum size for _undoStack and _redoStack, discarding the oldest entries when the limit is exceeded to prevent excessive memory usage.

Suggested implementation:

```

  void _pushToUndoStack() {
    _undoStack.add(_copyGrid(_drawViewGrid));
    _redoStack.clear(); // Invalidate redo stack on new action
  }
=======
  // ========== UNDO / REDO ==========

  static const int _maxUndoRedoStackSize = 50;

  void _pushToUndoStack() {
    _undoStack.add(_copyGrid(_drawViewGrid));
    if (_undoStack.length > _maxUndoRedoStackSize) {
      _undoStack.removeAt(0); // Remove the oldest entry
    }
    _redoStack.clear(); // Invalidate redo stack on new action
  }

```

```
  void undo() {
    if (_undoStack.isNotEmpty) {
      _redoStack.add(_copyGrid(_drawViewGrid));
      if (_redoStack.length > _maxUndoRedoStackSize) {
        _redoStack.removeAt(0); // Remove the oldest entry
      }
      _drawViewGrid = _undoStack.removeLast();

```
</issue_to_address>

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.

Copy link
Contributor

@nope3472 nope3472 left a comment

Choose a reason for hiding this comment

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

The undo and redo buttons are always enabled. It might be helpful to visually disable them (greyed out) when undo/redo is not available (i.e., when the stack is empty), to improve user feedback.

Copy link
Member

@mariobehling mariobehling left a comment

Choose a reason for hiding this comment

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

Please resolve conflicts.

Copy link
Contributor

github-actions bot commented Jul 30, 2025

Build Status

Build successful. APKs to test: https://github.com/fossasia/badgemagic-app/actions/runs/17409222203/artifacts/3908098980.

Screenshots

Android Screenshots
iPhone Screenshots
iPad Screenshots

Copy link
Contributor

@nope3472 nope3472 left a comment

Choose a reason for hiding this comment

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

just resolve the conflicts .
everything looks good.

@hpdang
Copy link
Member

hpdang commented Aug 7, 2025

@samruddhi-Rahegaonkar resolve conflict

Copy link
Contributor

@nope3472 nope3472 left a comment

Choose a reason for hiding this comment

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

@samruddhi-Rahegaonkar use the pull request.yml file from #1381 the test will run correctly

@samruddhi-Rahegaonkar
Copy link
Member Author

@nope3472 We can't hardcode it if we do that we need to change the versions for every bump. please go through #1394

@nope3472
Copy link
Contributor

@samruddhi-Rahegaonkar the stable one was failing for the animations pr so i looked at the available versions and used them.

@samruddhi-Rahegaonkar
Copy link
Member Author

samruddhi-Rahegaonkar commented Aug 10, 2025

@samruddhi-Rahegaonkar the stable one was failing for the animations pr so i looked at the available versions and used them.

Yeah, But that is not appropriate fix we need to figure out something to make it automated.

@hpdang
Copy link
Member

hpdang commented Aug 17, 2025

@samruddhi-Rahegaonkar please resolve conflicts

@samruddhi-Rahegaonkar
Copy link
Member Author

Hey, @hpdang We can merge this.

@samruddhi-Rahegaonkar
Copy link
Member Author

Hey, @hpdang We can merge this.

Hey @hpdang , are there any changes needed ?

@hpdang
Copy link
Member

hpdang commented Aug 20, 2025

@samruddhi-Rahegaonkar please see screenshot
WhatsApp Image 2025-08-20 at 6 19 04 PM

@samruddhi-Rahegaonkar
Copy link
Member Author

@samruddhi-Rahegaonkar please see screenshot WhatsApp Image 2025-08-20 at 6 19 04 PM

I will raise fix shortly.

@samruddhi-Rahegaonkar
Copy link
Member Author

Screenshot_20250820_222427 fixed!

@hpdang
Copy link
Member

hpdang commented Aug 20, 2025

@samruddhi-Rahegaonkar it looks like the undo/redo buttons are activated only after user clicks on Erase. Can you explain why the behavior of this function please?

@samruddhi-Rahegaonkar
Copy link
Member Author

samruddhi-Rahegaonkar commented Aug 21, 2025

@hpdang Undo/Redo Buttons are activated not after clicking on erase but when we move out from the shapes. undo redo functionality is only applied for freehand drawing. and it requires some trigger to get activated like clicking on shapes or draw again.

@hpdang
Copy link
Member

hpdang commented Aug 21, 2025

Let's show it live at the meeting today, I find some issues with this function

@samruddhi-Rahegaonkar
Copy link
Member Author

@hpdang Can we merge this ?

@mariobehling mariobehling changed the title feat: added undo/redo functionality for draw clipart feat: Added undo/redo functionality for drawing cliparts Sep 1, 2025
@hpdang
Copy link
Member

hpdang commented Sep 1, 2025

@samruddhi-Rahegaonkar please see screenshot WhatsApp Image 2025-08-20 at 6 19 04 PM

  • The bottom overflow line is still visible when selection the Shape function
  • When using the freehand drawing option, the undo button only responds on the second click, whereas with the Shape option, undo works immediately on the first click.

@samruddhi-Rahegaonkar
Copy link
Member Author

Screen.Recording.2025-09-02.at.9.37.55.PM.mov

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support for Undo/Redo functionality in Draw Clipart.
4 participants