commit bae0639fc8dfe72c3ac8338b3d592b6aa71a4ff2
parent 7dcc2e700dfcb315c05de40f07c299d91c7da128
Author: ajcoder13 <avnijhalani@gmail.com>
Date: Tue, 2 Dec 2025 06:47:54 +0530
Magic Draw functionality implemented, icons fixed, all bugs fixed
Diffstat:
2 files changed, 442 insertions(+), 185 deletions(-)
diff --git a/lib/ui/pages/canvas_board_page.dart b/lib/ui/pages/canvas_board_page.dart
@@ -109,6 +109,7 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
final FileService _fileService = FileService();
final ChangeStack _changeStack = ChangeStack();
final ImagePicker _picker = ImagePicker();
+ final ChangeStack _magicDrawChangeStack = ChangeStack();
// --- STATE ---
bool _hasUnsavedChanges = false;
@@ -139,6 +140,9 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
bool _isMagicDrawActive = false;
bool _isTextToolsActive = false;
+ bool _isMagicPanelDisabled = false;
+ bool _isViewMode = false;
+
// --- EDITING ---
bool _isEditingText = false;
final TextEditingController _textEditingController = TextEditingController();
@@ -175,7 +179,7 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
});
_hasUnsavedChanges = true;
}
-
+
// Note: injectedMedia handling is done inside _loadCanvasFromFile to ensure
// it happens after file content is loaded, avoiding race conditions.
}
@@ -197,6 +201,33 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
});
}
+ Future<bool> _confirmDiscardMagicDraw() async {
+ if (_magicPaths.isEmpty) return true; // nothing drawn – no popup
+
+ final result = await showDialog<bool>(
+ context: context,
+ builder:
+ (context) => AlertDialog(
+ title: const Text("Discard Magic Draw?"),
+ content: const Text(
+ "Leaving Magic Draw will remove your sketch. Continue?",
+ ),
+ actions: [
+ TextButton(
+ onPressed: () => Navigator.pop(context, false),
+ child: const Text("Stay"),
+ ),
+ TextButton(
+ onPressed: () => Navigator.pop(context, true),
+ child: const Text("Discard"),
+ ),
+ ],
+ ),
+ );
+
+ return result == true;
+ }
+
Future<void> _analyzeCanvas() async {
if (_isAnalyzing || _isInpainting) return; // Skip if busy
setState(() => _isAnalyzing = true);
@@ -217,7 +248,8 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
if (description != null && mounted) {
setState(() {
_aiDescription = description;
- _isDescriptionExpanded = false; // Reset to collapsed on new description
+ _isDescriptionExpanded =
+ false; // Reset to collapsed on new description
});
}
} catch (e) {
@@ -273,6 +305,17 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
}
// --- UNDO/REDO ---
+ // --- MAGIC DRAW UNDO/REDO (NEW) ---
+ void _recordMagicChange(List<DrawingPath> oldMagicPaths) {
+ final newMagicPaths = List<DrawingPath>.from(_magicPaths);
+ _magicDrawChangeStack.add(
+ Change(
+ oldMagicPaths,
+ () => setState(() => _magicPaths = List.from(newMagicPaths)), // Redo
+ (val) => setState(() => _magicPaths = List.from(val)), // Undo
+ ),
+ );
+ }
void _recordChange(CanvasState oldState) {
_resetInactivityTimer(); // Reset timer on undoable actions
@@ -431,10 +474,10 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
if (_tempBaseImage != null) return;
debugPrint("📸 [Magic Draw] Hiding strokes to capture clean base...");
-
+
// 1. Hide strokes by updating state
setState(() => _isCapturingBase = true);
-
+
// 2. Wait for frame to render
await Future.delayed(const Duration(milliseconds: 50));
@@ -534,6 +577,18 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
// ===========================================================================
// SAVE & LOAD LOGIC
// ===========================================================================
+ Future<void> _handleMagicDrawExit() async {
+ // Use the helper method you already wrote: _confirmDiscardMagicDraw
+ if (_magicPaths.isNotEmpty && !_isInpainting) {
+ final confirm = await _confirmDiscardMagicDraw();
+ if (confirm) {
+ _saveAndCloseMagicDraw();
+ }
+ // If false, do nothing (stay)
+ } else {
+ _saveAndCloseMagicDraw();
+ }
+ }
void _handleBackNavigation() {
// If in magic draw mode, just close tool first
@@ -757,9 +812,9 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
}
_hasUnsavedChanges = false;
});
-
+
// Handle Injected Media (e.g. from Share)
- if (widget.injectedMedia != null) {
+ if (widget.injectedMedia != null) {
final oldState = _getCurrentState();
setState(() {
elements.add({
@@ -840,7 +895,7 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
void _showComingSoon([dynamic feature]) => ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('Coming soon')));
-
+
// [UPDATED] New Bottom Sheet for Assets
void _openStylesheet() {
showModalBottomSheet(
@@ -939,7 +994,8 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
children: [
GestureDetector(
onTap: () {
- if (!_isMagicDrawActive) {
+ // <--- CHANGED: Allow deselecting if MagicDraw is OFF OR if Hand Mode (PanelDisabled) is ON
+ if (!_isMagicDrawActive || _isMagicPanelDisabled) {
_exitEditMode();
setState(() => selectedId = null);
}
@@ -951,10 +1007,10 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
boundaryMargin: const EdgeInsets.all(double.infinity),
minScale: 0.01,
maxScale: 10.0,
- scaleEnabled: !_isMagicDrawActive,
- panEnabled: !_isMagicDrawActive,
+ // <--- CHANGED: Enable Zoom/Pan if MagicDraw is OFF OR if Hand Mode (PanelDisabled) is ON
+ scaleEnabled: !_isMagicDrawActive || _isMagicPanelDisabled,
+ panEnabled: !_isMagicDrawActive || _isMagicPanelDisabled,
child: RepaintBoundary(
- // WRAPPED CANVAS IN REPAINT BOUNDARY
key: _canvasGlobalKey,
child: SizedBox(
width: _canvasSize.width,
@@ -986,6 +1042,8 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
type: e['type'],
content: e['content'],
styleData: e,
+ // <--- OPTIONAL: If you want to move elements while Hand is active, change this line too:
+ // isSelected: isSelected && (!_isMagicDrawActive || _isMagicPanelDisabled),
isSelected: isSelected && !_isMagicDrawActive,
isEditing:
isSelected &&
@@ -1004,7 +1062,6 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
_isTextToolsActive = true;
});
setState(() {
- // Bring to front
elements.remove(e);
elements.add(e);
});
@@ -1040,7 +1097,9 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
}),
// Drawing Layer
IgnorePointer(
- ignoring: !_isMagicDrawActive,
+ // <--- CHANGED: Ignore touches (disable drawing) if MagicDraw is OFF OR if Hand Mode (PanelDisabled) is ON
+ ignoring:
+ !_isMagicDrawActive || _isMagicPanelDisabled,
child: RepaintBoundary(
key: _drawingKey,
child: GestureDetector(
@@ -1054,10 +1113,12 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
size: Size.infinite,
painter: CanvasPainter(
paths: _paths,
- // CONDITIONALLY HIDE MAGIC PATHS DURING CAPTURE
- magicPaths: _isCapturingBase ? [] : _magicPaths,
- // CONDITIONALLY HIDE CURRENT POINTS DURING CAPTURE
- currentPoints: _isCapturingBase ? [] : _currentPoints,
+ magicPaths:
+ _isCapturingBase ? [] : _magicPaths,
+ currentPoints:
+ _isCapturingBase
+ ? []
+ : _currentPoints,
currentColor:
_isEraser
? Colors.transparent
@@ -1076,7 +1137,8 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
),
),
- // AI Description Banner
+ // ... (Rest of your UI: AI Description, MagicDrawTools, etc.) ...
+ // I have truncated the bottom part as it remains unchanged.
if (_aiDescription != null && !_isMagicDrawActive)
Positioned(
top: 10,
@@ -1152,13 +1214,18 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
strokeWidth: _strokeWidth,
isEraser: _isEraser,
brandColors: _brandColors,
- onClose: _saveAndCloseMagicDraw,
+ onClose: _handleMagicDrawExit,
onColorChanged: (c) => setState(() => _selectedColor = c),
onWidthChanged: (w) => setState(() => _strokeWidth = w),
onEraserToggle: (e) => setState(() => _isEraser = e),
- // CONNECTED CALLBACKS:
onPromptSubmit: (prompt) => _processInpainting(prompt),
isProcessing: _isInpainting,
+ onMagicPanelActivityToggle:
+ (disabled) =>
+ setState(() => _isMagicPanelDisabled = disabled),
+ isMagicPanelDisabled: _isMagicPanelDisabled,
+ onViewModeToggle:
+ (enabled) => setState(() => _isViewMode = enabled),
),
TextToolsOverlay(
@@ -1177,7 +1244,6 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
});
},
onAddText: _addTextElement,
- // onDelete: _deleteSelectedElement, // REMOVED
onColorChanged:
(c) =>
_updateSelectedTextProperty('style_color', c.value),
@@ -1194,20 +1260,105 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
_isMagicDrawActive
? "Magic Draw"
: (_isTextToolsActive ? "Text" : null),
- onMagicDraw:
- () => setState(() {
- _isMagicDrawActive = !_isMagicDrawActive;
- _isTextToolsActive = false;
- _exitEditMode();
- _magicPaths.clear(); // Clear old magic paths
- _tempBaseImage = null; // Force recapture next time
- }),
- onMedia: _pickImageFromGallery,
- onStylesheet: _openStylesheet,
- onTools: () => _showComingSoon('Tools'),
- onText: _toggleTextTools,
- onSelect: () => _showComingSoon('Select'),
- onPlugins: () => _showComingSoon('Plugins'),
+ onMagicDraw: () async {
+ if (_isMagicDrawActive) {
+ final confirm = await _confirmDiscardMagicDraw();
+ if (!confirm) return;
+ setState(() {
+ _magicPaths.clear();
+ _magicDrawChangeStack.clear();
+ _tempBaseImage = null;
+ _isMagicDrawActive = false;
+ });
+ return;
+ }
+ setState(() {
+ _isMagicDrawActive = true;
+ _isTextToolsActive = false;
+ _exitEditMode();
+ _magicPaths.clear();
+ _magicDrawChangeStack.clear();
+ _tempBaseImage = null;
+ });
+ },
+ onMedia: () async {
+ if (_isMagicDrawActive) {
+ final confirm = await _confirmDiscardMagicDraw();
+ if (!confirm) return;
+ setState(() {
+ _magicPaths.clear();
+ _magicDrawChangeStack.clear();
+ _tempBaseImage = null;
+ _isMagicDrawActive = false;
+ });
+ }
+ _pickImageFromGallery();
+ },
+ onStylesheet: () async {
+ if (_isMagicDrawActive) {
+ final confirm = await _confirmDiscardMagicDraw();
+ if (!confirm) return;
+ setState(() {
+ _magicPaths.clear();
+ _magicDrawChangeStack.clear();
+ _tempBaseImage = null;
+ _isMagicDrawActive = false;
+ });
+ }
+ _openStylesheet();
+ },
+ onTools: () async {
+ if (_isMagicDrawActive) {
+ final confirm = await _confirmDiscardMagicDraw();
+ if (!confirm) return;
+ setState(() {
+ _magicPaths.clear();
+ _magicDrawChangeStack.clear();
+ _tempBaseImage = null;
+ _isMagicDrawActive = false;
+ });
+ }
+ _showComingSoon('Tools');
+ },
+ onText: () async {
+ if (_isMagicDrawActive) {
+ final confirm = await _confirmDiscardMagicDraw();
+ if (!confirm) return;
+ setState(() {
+ _magicPaths.clear();
+ _magicDrawChangeStack.clear();
+ _tempBaseImage = null;
+ _isMagicDrawActive = false;
+ });
+ }
+ _toggleTextTools();
+ },
+ onSelect: () async {
+ if (_isMagicDrawActive) {
+ final confirm = await _confirmDiscardMagicDraw();
+ if (!confirm) return;
+ setState(() {
+ _magicPaths.clear();
+ _magicDrawChangeStack.clear();
+ _tempBaseImage = null;
+ _isMagicDrawActive = false;
+ });
+ }
+ _showComingSoon('Select');
+ },
+ onPlugins: () async {
+ if (_isMagicDrawActive) {
+ final confirm = await _confirmDiscardMagicDraw();
+ if (!confirm) return;
+ setState(() {
+ _magicPaths.clear();
+ _magicDrawChangeStack.clear();
+ _tempBaseImage = null;
+ _isMagicDrawActive = false;
+ });
+ }
+ _showComingSoon('Plugins');
+ },
),
),
],
@@ -1217,7 +1368,6 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
),
);
}
-
// --- DRAWING HELPERS ---
void _onPanUpdate(DragUpdateDetails details) {
@@ -1233,6 +1383,7 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
if (_currentPoints.isNotEmpty) {
if (_isMagicDrawActive) {
// ADD TO MAGIC PATHS (Temporary mask)
+ final oldMagicPaths = List<DrawingPath>.from(_magicPaths);
_magicPaths.add(
DrawingPath(
points: List.from(_currentPoints),
@@ -1241,6 +1392,7 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
isEraser: _isEraser,
),
);
+ _recordMagicChange(oldMagicPaths);
_currentPoints = [];
} else {
// NORMAL DRAWING
@@ -1286,7 +1438,7 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
File? maskFile = await _generateMaskImageFromPaths(
_magicPaths,
_canvasSize,
- _tempBaseImage,
+ _tempBaseImage,
);
if (maskFile == null) throw Exception("Failed to generate mask");
@@ -1298,7 +1450,6 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
);
_addGeneratedImage(newImageUrl);
-
} else {
// --- SKETCH-TO-IMAGE FLOW (New) ---
// Capture the entire canvas (strokes only since no images exist)
@@ -1313,7 +1464,6 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
_addGeneratedImage(newImageUrl);
}
-
} catch (e) {
debugPrint("Generation Error: $e");
ScaffoldMessenger.of(
@@ -1322,28 +1472,29 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
} finally {
setState(() {
_isInpainting = false;
- _tempBaseImage = null; // Reset base image
- _magicPaths.clear(); // Always clear magic paths on end
+ _tempBaseImage = null;
+ _magicPaths.clear();
+ _magicDrawChangeStack.clear(); // <--- ADD THIS
});
}
}
void _addGeneratedImage(String? newImageUrl) {
- if (newImageUrl != null) {
- debugPrint("✅ Adding generated image to canvas: $newImageUrl");
- setState(() {
- elements.add({
- 'id': 'gen_${DateTime.now().millisecondsSinceEpoch}',
- 'type': 'file_image',
- 'content': newImageUrl,
- 'position': const Offset(0, 0),
- 'size': _canvasSize,
- 'rotation': 0.0,
- });
- // Clear magic paths now that operation is done
- _magicPaths.clear();
+ if (newImageUrl != null) {
+ debugPrint("✅ Adding generated image to canvas: $newImageUrl");
+ setState(() {
+ elements.add({
+ 'id': 'gen_${DateTime.now().millisecondsSinceEpoch}',
+ 'type': 'file_image',
+ 'content': newImageUrl,
+ 'position': const Offset(0, 0),
+ 'size': _canvasSize,
+ 'rotation': 0.0,
});
- }
+ // Clear magic paths now that operation is done
+ _magicPaths.clear();
+ });
+ }
}
// Updated to generate mask as: Base Image + Drawing Strokes
@@ -1365,9 +1516,9 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
final codec = await ui.instantiateImageCodec(data);
final frameInfo = await codec.getNextFrame();
final baseImage = frameInfo.image;
-
+
paintImage(
- canvas: canvas,
+ canvas: canvas,
rect: Rect.fromLTWH(0, 0, size.width, size.height),
image: baseImage,
fit: BoxFit.cover, // Or contain, depending on your logic
@@ -1384,7 +1535,9 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
for (final path in paths) {
final paint =
Paint()
- ..color = path.color // Use the drawing color (e.g. Blue)
+ ..color =
+ path
+ .color // Use the drawing color (e.g. Blue)
..strokeWidth = path.strokeWidth
..strokeCap = StrokeCap.round
..strokeJoin = StrokeJoin.round
@@ -1398,11 +1551,9 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
}
canvas.drawPath(p, paint);
} else if (path.points.isNotEmpty) {
- canvas.drawPoints(
- ui.PointMode.points,
- [path.points.first.offset],
- paint,
- );
+ canvas.drawPoints(ui.PointMode.points, [
+ path.points.first.offset,
+ ], paint);
}
}
@@ -1426,16 +1577,24 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
}
}
- // Just toggles state; drawing data stays in _paths and is saved via _saveCanvas
Future<void> _saveAndCloseMagicDraw() async {
setState(() {
_isMagicDrawActive = false;
- _magicPaths.clear(); // Clear magic strokes if canceled
+ _magicPaths.clear();
+ _magicDrawChangeStack.clear();
_tempBaseImage = null;
});
}
PreferredSizeWidget _buildAppBar() {
+ final bool canUndo =
+ _isMagicDrawActive
+ ? _magicDrawChangeStack.canUndo
+ : _changeStack.canUndo;
+ final bool canRedo =
+ _isMagicDrawActive
+ ? _magicDrawChangeStack.canRedo
+ : _changeStack.canRedo;
return AppBar(
leadingWidth: 160,
leading: SafeArea(
@@ -1452,7 +1611,7 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
),
onPressed: () {
if (_isMagicDrawActive) {
- _saveAndCloseMagicDraw();
+ _handleMagicDrawExit();
} else {
_handleBackNavigation();
}
@@ -1468,8 +1627,13 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
),
),
onPressed:
- _changeStack.canUndo
- ? () => setState(() => _changeStack.undo())
+ canUndo
+ ? () => setState(
+ () =>
+ _isMagicDrawActive
+ ? _magicDrawChangeStack.undo()
+ : _changeStack.undo(),
+ )
: null,
),
IconButton(
@@ -1482,8 +1646,13 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> {
),
),
onPressed:
- _changeStack.canRedo
- ? () => setState(() => _changeStack.redo())
+ canRedo
+ ? () => setState(
+ () =>
+ _isMagicDrawActive
+ ? _magicDrawChangeStack.redo()
+ : _changeStack.redo(),
+ )
: null,
),
],
@@ -1849,12 +2018,12 @@ class CanvasPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
canvas.saveLayer(Rect.fromLTWH(0, 0, size.width, size.height), Paint());
-
+
// Draw normal paths
for (final path in paths) {
_drawPath(canvas, path);
}
-
+
// Draw magic paths (unless hidden by empty list passed in)
for (final path in magicPaths) {
_drawPath(canvas, path);
@@ -1880,26 +2049,24 @@ class CanvasPainter extends CustomPainter {
}
void _drawPath(Canvas canvas, DrawingPath path) {
- final paint =
- Paint()
- ..color = path.isEraser ? Colors.transparent : path.color
- ..blendMode = path.isEraser ? BlendMode.clear : BlendMode.srcOver
- ..strokeWidth = path.strokeWidth
- ..strokeCap = StrokeCap.round
- ..strokeJoin = StrokeJoin.round
- ..style = PaintingStyle.stroke;
-
- if (path.points.length > 1) {
- final Path p = Path();
- p.moveTo(path.points.first.offset.dx, path.points.first.offset.dy);
- for (int i = 1; i < path.points.length; i++)
- p.lineTo(path.points[i].offset.dx, path.points[i].offset.dy);
- canvas.drawPath(p, paint);
- } else if (path.points.isNotEmpty) {
- canvas.drawPoints(ui.PointMode.points, [
- path.points.first.offset,
- ], paint);
- }
+ final paint =
+ Paint()
+ ..color = path.isEraser ? Colors.transparent : path.color
+ ..blendMode = path.isEraser ? BlendMode.clear : BlendMode.srcOver
+ ..strokeWidth = path.strokeWidth
+ ..strokeCap = StrokeCap.round
+ ..strokeJoin = StrokeJoin.round
+ ..style = PaintingStyle.stroke;
+
+ if (path.points.length > 1) {
+ final Path p = Path();
+ p.moveTo(path.points.first.offset.dx, path.points.first.offset.dy);
+ for (int i = 1; i < path.points.length; i++)
+ p.lineTo(path.points[i].offset.dx, path.points[i].offset.dy);
+ canvas.drawPath(p, paint);
+ } else if (path.points.isNotEmpty) {
+ canvas.drawPoints(ui.PointMode.points, [path.points.first.offset], paint);
+ }
}
@override
diff --git a/lib/ui/pages/canvas_toolbar/magic_draw_overlay.dart b/lib/ui/pages/canvas_toolbar/magic_draw_overlay.dart
@@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
+// Note: You must ensure flutter_svg is imported in this file and installed in pubspec.yaml
+import 'package:flutter_svg/flutter_svg.dart';
class MagicDrawTools extends StatefulWidget {
final bool isActive;
@@ -10,9 +12,12 @@ class MagicDrawTools extends StatefulWidget {
final Function(double) onWidthChanged;
final Function(bool) onEraserToggle;
final VoidCallback onClose;
- final Function(String) onPromptSubmit; // Callback for prompt submission
- final bool isProcessing; // Loading state
+ final Function(String) onPromptSubmit;
+ final bool isProcessing;
final List<Color> brandColors;
+ final Function(bool) onViewModeToggle;
+ final Function(bool) onMagicPanelActivityToggle;
+ final bool isMagicPanelDisabled;
const MagicDrawTools({
super.key,
@@ -27,6 +32,9 @@ class MagicDrawTools extends StatefulWidget {
required this.onPromptSubmit,
required this.isProcessing,
required this.brandColors,
+ required this.onViewModeToggle,
+ required this.onMagicPanelActivityToggle,
+ required this.isMagicPanelDisabled,
});
@override
@@ -37,7 +45,6 @@ class _MagicDrawToolsState extends State<MagicDrawTools> {
bool _showStrokeSlider = false;
final TextEditingController _promptController = TextEditingController();
- // Initialize recent colors here so they persist
final List<Color> _recentColors = [
Colors.blue,
Colors.purple,
@@ -46,21 +53,33 @@ class _MagicDrawToolsState extends State<MagicDrawTools> {
Colors.amber,
];
+ bool _isViewMode = false;
+
@override
void dispose() {
+ if (_isViewMode) {
+ widget.onViewModeToggle(false);
+ }
_promptController.dispose();
super.dispose();
}
void _handleSubmit() {
- if (_promptController.text.trim().isNotEmpty && !widget.isProcessing) {
+ if (_promptController.text.trim().isNotEmpty &&
+ !widget.isProcessing &&
+ !widget.isMagicPanelDisabled) {
widget.onPromptSubmit(_promptController.text.trim());
- // Optional: Clear text after submit or keep it?
- // Usually keeping it is better for iterations, clearing if successful.
- // We'll let the parent decide or just keep it for now.
}
}
+ void _toggleViewMode() {
+ final newViewMode = !_isViewMode;
+ setState(() {
+ _isViewMode = newViewMode;
+ });
+ widget.onViewModeToggle(newViewMode);
+ }
+
@override
Widget build(BuildContext context) {
if (!widget.isActive) return const SizedBox.shrink();
@@ -80,23 +99,50 @@ class _MagicDrawToolsState extends State<MagicDrawTools> {
);
}
+ Widget _buildToolIcon(dynamic icon, bool isActive, VoidCallback onTap) {
+ final Color activeColor = Colors.black;
+ final Color inactiveColor = Colors.grey;
+ final Color iconColor = isActive ? activeColor : inactiveColor;
+
+ final Widget iconWidget =
+ (icon is IconData)
+ ? Icon(icon, size: 20, color: iconColor)
+ : SvgPicture.asset(
+ icon,
+ width: 20,
+ height: 20,
+ colorFilter: ColorFilter.mode(iconColor, BlendMode.srcIn),
+ );
+
+ return GestureDetector(
+ onTap: onTap,
+ child: Container(
+ padding: const EdgeInsets.all(10),
+ decoration: BoxDecoration(
+ color: isActive ? Colors.grey.shade200 : Colors.transparent,
+ shape: BoxShape.circle,
+ ),
+ child: iconWidget,
+ ),
+ );
+ }
+
Widget _buildMagicDrawPanel() {
return Container(
decoration: BoxDecoration(
color: Colors.white,
- borderRadius: BorderRadius.circular(24),
- boxShadow: [
+ borderRadius: BorderRadius.circular(16),
+ boxShadow: const [
BoxShadow(
- color: Colors.black.withOpacity(0.1),
- blurRadius: 15,
- offset: const Offset(0, 5),
+ color: Colors.black12,
+ blurRadius: 12,
+ offset: Offset(0, 4),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
- // Prompt Bar
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
@@ -136,49 +182,99 @@ class _MagicDrawToolsState extends State<MagicDrawTools> {
color: Color(0xFF2B2B2B),
shape: BoxShape.circle,
),
- child: widget.isProcessing
- ? const Padding(
- padding: EdgeInsets.all(12.0),
- child: CircularProgressIndicator(
+ child:
+ widget.isProcessing
+ ? const Padding(
+ padding: EdgeInsets.all(12.0),
+ child: CircularProgressIndicator(
+ color: Colors.white,
+ strokeWidth: 2,
+ ),
+ )
+ : const Icon(
+ Icons.auto_awesome,
color: Colors.white,
- strokeWidth: 2,
+ size: 20,
),
- )
- : const Icon(
- Icons.auto_awesome,
- color: Colors.white,
- size: 20,
- ),
),
),
],
),
),
Divider(height: 1, color: Colors.grey.shade200),
- // Tools Row
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
- _buildToolIcon(Icons.crop_free, false, () {}),
- _buildToolIcon(Icons.brush, !widget.isEraser, () {
- widget.onEraserToggle(false);
+ _buildToolIcon(Icons.pan_tool, widget.isMagicPanelDisabled, () {
+ setState(() => _showStrokeSlider = false);
+
+ // Deactivate drawing tools when hand icon is activated
+
+ if (!widget.isMagicPanelDisabled) {
+ widget.onEraserToggle(false);
+ }
+
+ widget.onMagicPanelActivityToggle(
+ !widget.isMagicPanelDisabled,
+ );
}),
+ _buildToolIcon(
+ 'assets/icons/brush-line.svg',
+
+ !widget.isEraser &&
+ !_isViewMode &&
+ !widget.isMagicPanelDisabled,
+
+ () {
+ setState(() => _showStrokeSlider = false);
+
+ if (_isViewMode) {
+ setState(() => _isViewMode = false);
+
+ widget.onViewModeToggle(false);
+ }
+
+ // Deactivate hand icon when brush is activated
+
+ if (widget.isMagicPanelDisabled) {
+ widget.onMagicPanelActivityToggle(false);
+ }
+
+ widget.onEraserToggle(false);
+ },
+ ),
- // Color Picker Trigger
GestureDetector(
- onTap: () => _showAdvancedColorPicker(context),
+ onTap: () {
+ setState(() => _showStrokeSlider = false);
+
+ if (_isViewMode) {
+ setState(() => _isViewMode = false);
+
+ widget.onViewModeToggle(false);
+ }
+
+ _showAdvancedColorPicker(context);
+ },
+
child: Container(
width: 28,
+
height: 28,
+
decoration: BoxDecoration(
color: widget.selectedColor,
+
shape: BoxShape.circle,
+
border: Border.all(color: Colors.white, width: 2),
+
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
+
blurRadius: 4,
),
],
@@ -187,31 +283,64 @@ class _MagicDrawToolsState extends State<MagicDrawTools> {
),
GestureDetector(
- onTap: () => setState(
- () => _showStrokeSlider = !_showStrokeSlider,
- ),
+ onTap: () {
+ if (_isViewMode) {
+ setState(() => _isViewMode = false);
+
+ widget.onViewModeToggle(false);
+ }
+
+ setState(() => _showStrokeSlider = !_showStrokeSlider);
+ },
+
child: Container(
padding: const EdgeInsets.all(8),
+
decoration: BoxDecoration(
- color: _showStrokeSlider
- ? Colors.grey.shade200
- : Colors.transparent,
+ color:
+ _showStrokeSlider
+ ? Colors.grey.shade200
+ : Colors.transparent,
+
shape: BoxShape.circle,
),
+
child: Container(
width: 10,
+
height: 10,
+
decoration: const BoxDecoration(
color: Colors.black87,
+
shape: BoxShape.circle,
),
),
),
),
+
_buildToolIcon(
- Icons.cleaning_services_outlined,
- widget.isEraser,
+ 'assets/icons/eraser-line.svg',
+
+ widget.isEraser &&
+ !_isViewMode &&
+ !widget.isMagicPanelDisabled,
+
() {
+ setState(() => _showStrokeSlider = false);
+
+ if (_isViewMode) {
+ setState(() => _isViewMode = false);
+
+ widget.onViewModeToggle(false);
+ }
+
+ // Deactivate hand icon when eraser is activated
+
+ if (widget.isMagicPanelDisabled) {
+ widget.onMagicPanelActivityToggle(false);
+ }
+
widget.onEraserToggle(true);
},
),
@@ -223,20 +352,6 @@ class _MagicDrawToolsState extends State<MagicDrawTools> {
);
}
- Widget _buildToolIcon(IconData icon, bool isActive, VoidCallback onTap) {
- return GestureDetector(
- onTap: onTap,
- child: Container(
- padding: const EdgeInsets.all(10),
- decoration: BoxDecoration(
- color: isActive ? Colors.grey.shade200 : Colors.transparent,
- shape: BoxShape.circle,
- ),
- child: Icon(icon, size: 20, color: Colors.black54),
- ),
- );
- }
-
Widget _buildTaperedStrokeSlider() {
return Container(
width: 250,
@@ -269,8 +384,8 @@ class _MagicDrawToolsState extends State<MagicDrawTools> {
width: 210,
child: Slider(
value: widget.strokeWidth,
- min: 2.0,
- max: 30.0,
+ min: 5.0,
+ max: 60.0,
onChanged: widget.onWidthChanged,
),
),
@@ -300,9 +415,10 @@ class _MagicDrawToolsState extends State<MagicDrawTools> {
class _TaperedSliderPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
- final paint = Paint()
- ..color = const Color(0xFF2B2B2B)
- ..style = PaintingStyle.fill;
+ final paint =
+ Paint()
+ ..color = const Color(0xFF2B2B2B)
+ ..style = PaintingStyle.fill;
final path = Path();
path.moveTo(10, size.height / 2 - 2);
path.lineTo(size.width - 10, size.height / 2 - 10);
@@ -340,7 +456,6 @@ class _AdvancedColorPickerSheetState extends State<_AdvancedColorPickerSheet>
late Color _currentColor;
late List<Color> _brandPalette;
- // Controllers for RGB Sliders (Integrated from other branch)
final TextEditingController _rController = TextEditingController();
final TextEditingController _gController = TextEditingController();
final TextEditingController _bController = TextEditingController();
@@ -350,7 +465,7 @@ class _AdvancedColorPickerSheetState extends State<_AdvancedColorPickerSheet>
super.initState();
_currentColor = widget.initialColor;
_tabController = TabController(length: 3, vsync: this);
- _brandPalette = widget.brandColors; // Integrate real brand colors
+ _brandPalette = widget.brandColors;
_updateControllers();
}
@@ -372,14 +487,11 @@ class _AdvancedColorPickerSheetState extends State<_AdvancedColorPickerSheet>
void _updateColor(Color color) {
setState(() {
_currentColor = color;
-
- // Update recent colors list
widget.recentColors.removeWhere((c) => c.value == color.value);
widget.recentColors.insert(0, color);
if (widget.recentColors.length > 5) {
widget.recentColors.removeLast();
}
-
_updateControllers();
});
widget.onColorChanged(color);
@@ -396,7 +508,6 @@ class _AdvancedColorPickerSheetState extends State<_AdvancedColorPickerSheet>
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
- // --- HEADER HANDLE ---
const SizedBox(height: 12),
Center(
child: Container(
@@ -409,8 +520,6 @@ class _AdvancedColorPickerSheetState extends State<_AdvancedColorPickerSheet>
),
),
const SizedBox(height: 16),
-
- // --- TABS ---
TabBar(
controller: _tabController,
labelColor: Colors.black,
@@ -427,10 +536,7 @@ class _AdvancedColorPickerSheetState extends State<_AdvancedColorPickerSheet>
Tab(text: 'Sliders'),
],
),
-
const SizedBox(height: 16),
-
- // --- HEX / HEADER ROW ---
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
@@ -499,11 +605,8 @@ class _AdvancedColorPickerSheetState extends State<_AdvancedColorPickerSheet>
],
),
),
-
const SizedBox(height: 16),
const Divider(height: 1),
-
- // --- MAIN CONTENT AREA ---
Expanded(
child: TabBarView(
controller: _tabController,
@@ -515,34 +618,25 @@ class _AdvancedColorPickerSheetState extends State<_AdvancedColorPickerSheet>
],
),
),
-
- // --- FOOTER ---
_buildSharedFooter(),
],
),
);
}
- // --- HELPER: Generate colors (Integrated from other branch) ---
List<Color> _generateColorGrid() {
List<Color> colors = [];
-
- // 1. Top Row: Grayscale (White -> Black)
for (int i = 0; i < 9; i++) {
- double lightness = 1.0 - (i / 8); // 1.0 to 0.0
+ double lightness = 1.0 - (i / 8);
colors.add(HSLColor.fromAHSL(1.0, 0.0, 0.0, lightness).toColor());
}
-
- // 2. Main Grid: Hues (Columns) x Shades (Rows)
- final int hueSteps = 9; // Columns
- final int shadeSteps = 7; // Rows excluding grayscale
-
+ final int hueSteps = 9;
+ final int shadeSteps = 7;
for (int shade = 0; shade < shadeSteps; shade++) {
for (int hueStep = 0; hueStep < hueSteps; hueStep++) {
double hue = (hueStep / hueSteps) * 360;
double saturation = 0.5 + (shade / shadeSteps) * 0.5;
double lightness = 0.8 - (shade / shadeSteps) * 0.5;
-
colors.add(
HSLColor.fromAHSL(1.0, hue, saturation, lightness).toColor(),
);
@@ -657,14 +751,11 @@ class _AdvancedColorPickerSheetState extends State<_AdvancedColorPickerSheet>
_updateColor(_currentColor.withRed(v.toInt()));
}),
const SizedBox(height: 24),
- _buildSingleRGBSlider(
- "Green",
- Colors.green,
- _currentColor.green,
- (v) {
- _updateColor(_currentColor.withGreen(v.toInt()));
- },
- ),
+ _buildSingleRGBSlider("Green", Colors.green, _currentColor.green, (
+ v,
+ ) {
+ _updateColor(_currentColor.withGreen(v.toInt()));
+ }),
const SizedBox(height: 24),
_buildSingleRGBSlider("Blue", Colors.blue, _currentColor.blue, (v) {
_updateColor(_currentColor.withBlue(v.toInt()));
@@ -834,4 +925,4 @@ class _AdvancedColorPickerSheetState extends State<_AdvancedColorPickerSheet>
child: const Icon(Icons.add, size: 20, color: Colors.black54),
);
}
-}
-\ No newline at end of file
+}