Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 65 additions & 71 deletions lib/cubit/canvas_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,35 @@ import 'package:path/path.dart' as path;
import 'package:texterra/utils/custom_snackbar.dart';
import '../models/text_item_model.dart';
import '../models/draw_model.dart';
import '../models/history_entry.dart';
import 'canvas_state.dart';

class CanvasCubit extends Cubit<CanvasState> {
final ImagePicker _imagePicker = ImagePicker();

CanvasCubit() : super(CanvasState.initial());

// Helper method to add a new state to history
void _addToHistory(CanvasState newState, {String? actionDescription}) {
final newEntry = HistoryEntry(
state: newState,
timestamp: DateTime.now(),
actionDescription: actionDescription,
);

// If we're not at the end of history, truncate history after current index
final baseHistory = state.currentHistoryIndex < state.history.length - 1
? state.history.sublist(0, state.currentHistoryIndex + 1)
: state.history;

final newHistory = [...baseHistory, newEntry];

emit(newState.copyWith(
history: newHistory,
currentHistoryIndex: newHistory.length - 1,
));
}

//method to toggle the color tray
void toggleTray() {
emit(state.copyWith(isTrayShown: !state.isTrayShown));
Expand All @@ -40,7 +62,7 @@ class CanvasCubit extends Cubit<CanvasState> {
updatedItems[index] = updatedItems[index].copyWith(
hasShadow: !updatedItems[index].hasShadow,
);
_updateState(textItems: updatedItems);
_updateState(textItems: updatedItems, actionDescription: 'Toggled text shadow');
}

// Change shadow color
Expand All @@ -52,7 +74,7 @@ class CanvasCubit extends Cubit<CanvasState> {
hasShadow: true, // Automatically enable shadow when changing color
shadowColor: color,
);
_updateState(textItems: updatedItems);
_updateState(textItems: updatedItems, actionDescription: 'Changed shadow color');
}

// Change shadow blur radius
Expand Down Expand Up @@ -185,14 +207,11 @@ class CanvasCubit extends Cubit<CanvasState> {
color: ColorConstants.uiWhite, // My Default color for the text
);
final updatedItems = List<TextItem>.from(state.textItems)..add(newTextItem);
emit(
state.copyWith(
textItems: updatedItems,
selectedTextItemIndex: updatedItems.length - 1,
history: [...state.history, state],
future: [],
),
final newState = state.copyWith(
textItems: updatedItems,
selectedTextItemIndex: updatedItems.length - 1,
);
_addToHistory(newState, actionDescription: 'Added text: "${text.length > 20 ? '${text.substring(0, 20)}...' : text}"');
}

// Method to toggle text highlighting
Expand Down Expand Up @@ -246,7 +265,7 @@ class CanvasCubit extends Cubit<CanvasState> {

// method to change background color
void changeBackgroundColor(Color color) {
_updateState(backgroundColor: color);
_updateState(backgroundColor: color, actionDescription: 'Changed background color');
}

// Method to upload background image from gallery
Expand Down Expand Up @@ -437,24 +456,24 @@ class CanvasCubit extends Cubit<CanvasState> {

// method to undo changes and emit it
void undo() {
if (state.history.isNotEmpty) {
final previousState = state.history.last;
final newHistory = List<CanvasState>.from(state.history)..removeLast();
emit(previousState.copyWith(
history: newHistory,
future: [state, ...state.future],
if (state.currentHistoryIndex > 0) {
final newIndex = state.currentHistoryIndex - 1;
final targetState = state.history[newIndex].state;
emit(targetState.copyWith(
history: state.history,
currentHistoryIndex: newIndex,
));
}
}

// method to redo changes and emit it
void redo() {
if (state.future.isNotEmpty) {
final nextState = state.future.first;
final newFuture = List<CanvasState>.from(state.future)..removeAt(0);
emit(nextState.copyWith(
future: newFuture,
history: [...state.history, state],
if (state.currentHistoryIndex < state.history.length - 1) {
final newIndex = state.currentHistoryIndex + 1;
final targetState = state.history[newIndex].state;
emit(targetState.copyWith(
history: state.history,
currentHistoryIndex: newIndex,
));
}
}
Expand All @@ -466,19 +485,16 @@ class CanvasCubit extends Cubit<CanvasState> {
_deleteImageFile(state.backgroundImagePath!);
}

emit(
state.copyWith(
textItems: [],
drawPaths: [],
history: [...state.history, state],
future: [],
selectedTextItemIndex: null,
deselect: true,
isDrawingMode: false, // Exit drawing mode when clearing
clearCurrentPageName: true,
clearBackgroundImage: true,
),
final newState = state.copyWith(
textItems: [],
drawPaths: [],
selectedTextItemIndex: null,
deselect: true,
isDrawingMode: false, // Exit drawing mode when clearing
clearCurrentPageName: true,
clearBackgroundImage: true,
);
_addToHistory(newState, actionDescription: 'Cleared canvas');
CustomSnackbar.showInfo('Canvas cleared');
}

Expand All @@ -487,26 +503,26 @@ class CanvasCubit extends Cubit<CanvasState> {
Color? backgroundColor,
String? backgroundImagePath,
bool clearBackgroundImage = false,
String? actionDescription,
}) {
final newState = state.copyWith(
textItems: textItems ?? state.textItems,
backgroundColor: backgroundColor,
backgroundImagePath: backgroundImagePath,
clearBackgroundImage: clearBackgroundImage,
history: [...state.history, state],
future: [],
);
emit(newState);
_addToHistory(newState, actionDescription: actionDescription);
}

void deleteText(int index) {
final deletedItem = state.textItems[index];
final updatedList = List<TextItem>.from(state.textItems)..removeAt(index);
emit(state.copyWith(
textItems: updatedList,
selectedTextItemIndex: null,
history: [...state.history, state],
future: [],
deselect: true));
final newState = state.copyWith(
textItems: updatedList,
selectedTextItemIndex: null,
deselect: true,
);
_addToHistory(newState, actionDescription: 'Deleted text: "${deletedItem.text.length > 20 ? '${deletedItem.text.substring(0, 20)}...' : deletedItem.text}"');
}

Future<void> savePage(String pageName, {String? label, int? color}) async {
Expand Down Expand Up @@ -955,16 +971,8 @@ class CanvasCubit extends Cubit<CanvasState> {
);

final newPaths = List<DrawPath>.from(state.drawPaths)..add(newPath);

// Save current state to history
final historyState = state.copyWith();
final newHistory = List<CanvasState>.from(state.history)..add(historyState);

emit(state.copyWith(
drawPaths: newPaths,
history: newHistory,
future: [], // Clear future as we've made a new action
));
final newState = state.copyWith(drawPaths: newPaths);
_addToHistory(newState, actionDescription: 'Started drawing');
}

// Update the current drawing path with a new point
Expand Down Expand Up @@ -1001,35 +1009,21 @@ class CanvasCubit extends Cubit<CanvasState> {
void clearDrawings() {
if (state.drawPaths.isEmpty) return;

// Save current state to history
final historyState = state.copyWith();
final newHistory = List<CanvasState>.from(state.history)..add(historyState);

emit(state.copyWith(
drawPaths: [],
history: newHistory,
future: [], // Clear future as we've made a new action
));
final newState = state.copyWith(drawPaths: []);
_addToHistory(newState, actionDescription: 'Cleared all drawings');
CustomSnackbar.showInfo('Drawings cleared');
}

// Undo the last drawing stroke
void undoLastDrawing() {
if (state.drawPaths.isEmpty) return;

// Save current state to history
final historyState = state.copyWith();
final newHistory = List<CanvasState>.from(state.history)..add(historyState);

// Remove the last path
final newPaths = List<DrawPath>.from(state.drawPaths);
newPaths.removeLast();

emit(state.copyWith(
drawPaths: newPaths,
history: newHistory,
future: [], // Clear future as we've made a new action
));
final newState = state.copyWith(drawPaths: newPaths);
_addToHistory(newState, actionDescription: 'Undid last drawing stroke');

CustomSnackbar.showInfo('Last stroke undone');
}
Expand Down
33 changes: 21 additions & 12 deletions lib/cubit/canvas_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import 'package:flutter/material.dart';
import '../models/text_item_model.dart';
import '../constants/color_constants.dart';
import '../models/draw_model.dart';
import '../models/history_entry.dart';

class CanvasState {
final List<TextItem> textItems;
final List<DrawPath> drawPaths;
final List<CanvasState> history;
final List<CanvasState> future;
final List<HistoryEntry> history;
final int currentHistoryIndex;
final Color backgroundColor;
final String? backgroundImagePath;
final int? selectedTextItemIndex;
Expand All @@ -22,7 +23,7 @@ class CanvasState {
required this.textItems,
required this.drawPaths,
required this.history,
required this.future,
required this.currentHistoryIndex,
this.backgroundColor = ColorConstants.backgroundDarkGray,
this.backgroundImagePath,
this.selectedTextItemIndex,
Expand All @@ -35,11 +36,11 @@ class CanvasState {
});

factory CanvasState.initial() {
return const CanvasState(
final initialState = const CanvasState(
textItems: [],
drawPaths: [],
history: [],
future: [],
currentHistoryIndex: 0,
backgroundColor: ColorConstants.backgroundDarkGray,
backgroundImagePath: null,
selectedTextItemIndex: null,
Expand All @@ -50,13 +51,25 @@ class CanvasState {
currentStrokeWidth: 5.0,
currentBrushType: BrushType.brush,
);

// Add initial state to history
final initialEntry = HistoryEntry(
state: initialState,
timestamp: DateTime.now(),
actionDescription: 'Initial state',
);

return initialState.copyWith(
history: [initialEntry],
currentHistoryIndex: 0,
);
}

CanvasState copyWith({
List<TextItem>? textItems,
List<DrawPath>? drawPaths,
List<CanvasState>? history,
List<CanvasState>? future,
List<HistoryEntry>? history,
int? currentHistoryIndex,
Color? backgroundColor,
String? backgroundImagePath,
bool clearBackgroundImage = false,
Expand All @@ -74,7 +87,7 @@ class CanvasState {
textItems: textItems ?? this.textItems,
drawPaths: drawPaths ?? this.drawPaths,
history: history ?? this.history,
future: future ?? this.future,
currentHistoryIndex: currentHistoryIndex ?? this.currentHistoryIndex,
backgroundColor: backgroundColor ?? this.backgroundColor,
backgroundImagePath: clearBackgroundImage
? null
Expand Down Expand Up @@ -104,8 +117,6 @@ class CanvasState {
backgroundImagePath == other.backgroundImagePath &&
selectedTextItemIndex == other.selectedTextItemIndex &&
isDrawingMode == other.isDrawingMode &&
history == other.history &&
future == other.future &&
isTrayShown == other.isTrayShown &&
currentPageName == other.currentPageName &&
currentDrawColor == other.currentDrawColor &&
Expand All @@ -119,8 +130,6 @@ class CanvasState {
backgroundColor.hashCode ^
backgroundImagePath.hashCode ^
selectedTextItemIndex.hashCode ^
history.hashCode ^
future.hashCode ^
isTrayShown.hashCode ^
isDrawingMode.hashCode ^
currentDrawColor.hashCode ^
Expand Down
31 changes: 31 additions & 0 deletions lib/models/history_entry.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'canvas_state.dart';

class HistoryEntry {
final CanvasState state;
final DateTime timestamp;
final String? actionDescription;
ui.Image? thumbnail;

HistoryEntry({
required this.state,
required this.timestamp,
this.actionDescription,
this.thumbnail,
});

HistoryEntry copyWith({
CanvasState? state,
DateTime? timestamp,
String? actionDescription,
ui.Image? thumbnail,
}) {
return HistoryEntry(
state: state ?? this.state,
timestamp: timestamp ?? this.timestamp,
actionDescription: actionDescription ?? this.actionDescription,
thumbnail: thumbnail ?? this.thumbnail,
);
}
}
Loading