-
Notifications
You must be signed in to change notification settings - Fork 241
feat: Added undo/redo functionality for drawing cliparts #1378
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: development
Are you sure you want to change the base?
feat: Added undo/redo functionality for drawing cliparts #1378
Conversation
Reviewer's GuideThis 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 previewsequenceDiagram
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()
Sequence diagram for toggling and using shape toolssequenceDiagram
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()
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this 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>
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this 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>
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this 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.
7371fb3
to
5f1fd8b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please resolve conflicts.
1b822ff
to
8751896
Compare
Build StatusBuild successful. APKs to test: https://github.com/fossasia/badgemagic-app/actions/runs/17409222203/artifacts/3908098980. Screenshots |
There was a problem hiding this 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.
887546b
to
8751896
Compare
@samruddhi-Rahegaonkar resolve conflict |
There was a problem hiding this 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 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. |
856cd7f
to
8751896
Compare
d91c731
to
2bb692c
Compare
@samruddhi-Rahegaonkar please resolve conflicts |
Hey, @hpdang We can merge this. |
@samruddhi-Rahegaonkar please see screenshot |
I will raise fix shortly. |
8b0532d
to
448c658
Compare
@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? |
@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. |
Let's show it live at the meeting today, I find some issues with this function |
6d121b7
to
fe6885c
Compare
d3578fb
to
b16fc0b
Compare
@hpdang Can we merge this ? |
|
436c79f
to
5e32be6
Compare
Screen.Recording.2025-09-02.at.9.37.55.PM.mov |
Fixes #1377
Changes
Screenshots / Recordings
Checklist:
constants.dart
without hard coding any value.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:
Enhancements: