creek

The AI Image Editor of 2030
commit 8cef5fbd1dba3264a6c0a7531043aa6a3bffd4f0
parent e4bd0322d138b3770f2eda0bd1840b8fa984da82
Author: maydayv7 <maydayv7@gmail.com>
Date:   Wed, 10 Dec 2025 02:16:53 +0530

Fix canvas save errors

Diffstat:
Mlib/data/models/canvas_models.dart | 29++++++++++++++++++++++++++---
Mlib/services/file_service.dart | 6++++--
Mlib/ui/pages/canvas_page.dart | 110++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
3 files changed, 100 insertions(+), 45 deletions(-)

diff --git a/lib/data/models/canvas_models.dart b/lib/data/models/canvas_models.dart @@ -112,17 +112,40 @@ class ImageLayer extends CanvasLayer { @override Map<String, dynamic> toMap() { - return {'id': id, 'type': 'image', 'data': data, 'isVisible': isVisible}; + final encodableData = Map<String, dynamic>.from(data); + + if (encodableData['position'] is Offset) { + final pos = encodableData['position'] as Offset; + encodableData['position'] = {'dx': pos.dx, 'dy': pos.dy}; + } + + if (encodableData['size'] is Size) { + final size = encodableData['size'] as Size; + encodableData['size'] = {'width': size.width, 'height': size.height}; + } + + return { + 'id': id, + 'type': 'image', + 'data': encodableData, + 'isVisible': isVisible, + }; } factory ImageLayer.fromMap(Map<String, dynamic> map) { final data = Map<String, dynamic>.from(map['data']); // Restore Offset/Size objects if (data['position'] is Map) { - data['position'] = Offset(data['position']['dx'], data['position']['dy']); + data['position'] = Offset( + (data['position']['dx'] as num).toDouble(), + (data['position']['dy'] as num).toDouble(), + ); } if (data['size'] is Map) { - data['size'] = Size(data['size']['width'], data['size']['height']); + data['size'] = Size( + (data['size']['width'] as num).toDouble(), + (data['size']['height'] as num).toDouble(), + ); } return ImageLayer( diff --git a/lib/services/file_service.dart b/lib/services/file_service.dart @@ -89,7 +89,7 @@ class FileService { } // Saves canvas JSON structure to a file - Future<void> saveCanvasFile({ + Future<String> saveCanvasFile({ required int projectId, required Map<String, dynamic> canvasData, String? fileId, @@ -105,6 +105,7 @@ class FileService { await file.writeAsString(jsonString); await _repo.touchFile(fileId); // Update last modified } + return fileId; } else { // Create new file via temporary storage final directory = await getTemporaryDirectory(); @@ -113,7 +114,7 @@ class FileService { ); await tempFile.writeAsString(jsonString); - await saveFile( + final newId = await saveFile( tempFile, projectId, name: fileName ?? "Untitled Canvas", @@ -122,6 +123,7 @@ class FileService { // Cleanup temp file if (await tempFile.exists()) await tempFile.delete(); + return newId; } } diff --git a/lib/ui/pages/canvas_page.dart b/lib/ui/pages/canvas_page.dart @@ -66,6 +66,8 @@ class _CanvasPageState extends State<CanvasPage> { final ChangeStack _magicDrawChangeStack = ChangeStack(); bool _hasUnsavedChanges = false; + bool _canPop = false; + String? _currentFileId; // Layer state // Bottom-most layer is at index 0 @@ -129,6 +131,8 @@ class _CanvasPageState extends State<CanvasPage> { void initState() { super.initState(); _canvasSize = Size(widget.width, widget.height); + _currentFileId = widget.existingFile?.id; + _fetchBrandColors(); if (widget.existingFile != null) { @@ -185,8 +189,9 @@ class _CanvasPageState extends State<CanvasPage> { // Handle inactivity timer logic void _resetInactivityTimer() { _inactivityTimer?.cancel(); - _inactivityTimer = Timer(const Duration(seconds: 2), () { - _analyzeCanvas(); + // Only analyze if changes happened + _inactivityTimer = Timer(const Duration(seconds: 3), () { + if (_hasUnsavedChanges) _analyzeCanvas(); }); } @@ -437,8 +442,9 @@ class _CanvasPageState extends State<CanvasPage> { prompt: prompt, ); } - } else { + } else // Case 2: Sketch to Image + { if (modelId == 'sketch_fusion') { newImageUrl = await FlaskService().sketchToImage( projectId: widget.projectId, @@ -514,7 +520,7 @@ class _CanvasPageState extends State<CanvasPage> { final bool showTextOverlay = _isTextToolsActive || isTextSelected; return PopScope( - canPop: false, + canPop: _canPop, onPopInvoked: (didPop) { if (didPop) return; _handleBackNavigation(); @@ -884,26 +890,30 @@ class _CanvasPageState extends State<CanvasPage> { ), // Layers Panel - Positioned( - right: 16, - top: - SafeArea(child: Container()).minimum.top + - (_isMagicDrawActive ? 80 : 10), // Prevent overlap with description overlay - child: LayersPanel( - layers: _layers, - activeLayerId: _activeLayerId, - isOpen: _showLayersPanel, - onToggle: - () => setState( - () => _showLayersPanel = !_showLayersPanel, - ), - onReorder: _handleLayerReorder, - onDelete: _handleLayerDelete, - onToggleVisibility: _handleLayerVisibility, - onLayerTap: _setActiveLayer, - onAddLayer: _isMagicDrawActive ? _addNewMagicLayer : null, + if (!(_isMagicDrawActive && _isDescriptionExpanded)) + Positioned( + right: 16, + // Prevent overlap with description overlay + top: + SafeArea(child: Container()).minimum.top + + ((_isMagicDrawActive && _aiDescription != null) + ? 80 + : 10), + child: LayersPanel( + layers: _layers, + activeLayerId: _activeLayerId, + isOpen: _showLayersPanel, + onToggle: + () => setState( + () => _showLayersPanel = !_showLayersPanel, + ), + onReorder: _handleLayerReorder, + onDelete: _handleLayerDelete, + onToggleVisibility: _handleLayerVisibility, + onLayerTap: _setActiveLayer, + onAddLayer: _isMagicDrawActive ? _addNewMagicLayer : null, + ), ), - ), Positioned( bottom: 0, @@ -1431,8 +1441,12 @@ class _CanvasPageState extends State<CanvasPage> { _handleMagicDrawExit(); return; } - if (!_hasUnsavedChanges && widget.existingFile != null) { - Navigator.pop(context); + // If no changes, allow pop + if (!_hasUnsavedChanges && _currentFileId != null) { + setState(() => _canPop = true); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) Navigator.pop(context); + }); return; } ShowDialog.show( @@ -1443,12 +1457,18 @@ class _CanvasPageState extends State<CanvasPage> { onPrimaryPressed: () async { Navigator.pop(context); await _saveCanvas(); - if (mounted && widget.existingFile != null) Navigator.pop(context); + setState(() => _canPop = true); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) Navigator.pop(context); + }); }, secondaryButtonText: "Discard", onSecondaryPressed: () { Navigator.pop(context); - Navigator.pop(context); + setState(() => _canPop = true); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) Navigator.pop(context); + }); }, ); } @@ -1506,13 +1526,18 @@ class _CanvasPageState extends State<CanvasPage> { Future<void> _saveCanvas() async { try { String fileName = "Canvas ${DateTime.now().toString().split(' ')[0]}"; - if (widget.existingFile == null) { + // If new file, ask for name + if (_currentFileId == null) { final userFileName = await _showNameDialog(); if (userFileName == null || userFileName.isEmpty) return; fileName = userFileName; } final String? previewPath = await _generatePreviewImage(); - final layersJson = _layers.map((l) => l.toMap()).toList(); + final layersJson = + _layers + .where((l) => !(l is SketchLayer && l.isMagicDraw)) + .map((l) => l.toMap()) + .toList(); final canvasData = { 'version': 2, 'layers': layersJson, @@ -1520,27 +1545,32 @@ class _CanvasPageState extends State<CanvasPage> { 'height': _canvasSize.height, 'preview_path': previewPath, }; - await _fileService.saveCanvasFile( + // Pass current ID and capture returned ID + final savedId = await _fileService.saveCanvasFile( projectId: widget.projectId, canvasData: canvasData, - fileId: widget.existingFile?.id, - fileName: widget.existingFile == null ? fileName : null, + fileId: _currentFileId, + fileName: _currentFileId == null ? fileName : null, ); - setState(() => _hasUnsavedChanges = false); if (mounted) { + setState(() { + _currentFileId = savedId; + _hasUnsavedChanges = false; + }); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text("Canvas Saved Successfully")), ); - if (widget.existingFile == null) - Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (_) => ProjectFilePage(projectId: widget.projectId), - ), - ); } } catch (e) { debugPrint("Save Error: $e"); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text("Failed to save: $e"), + backgroundColor: Colors.red, + ), + ); + } } }