creek

The AI Image Editor of 2030
commit 95bd819cb97765ce5317c8810f5cc7b18f8786d3
parent 975178067a60a85b4ab5d4cd506de1a6f1ae4d28
Author: maydayv7 <maydayv7@gmail.com>
Date:   Thu,  4 Dec 2025 10:36:31 +0530

Add project rename/delete

Also remove some comments

Diffstat:
Mlib/data/models/image_model.dart | 2+-
Mlib/data/repos/file_repo.dart | 4++--
Mlib/services/file_service.dart | 7++-----
Mlib/ui/pages/canvas_page.dart | 6+++---
Mlib/ui/pages/home_page.dart | 200++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mlib/ui/pages/image_details_page.dart | 13+++++--------
Mlib/ui/pages/image_save_page.dart | 14+++-----------
Mlib/ui/pages/project_board_page.dart | 2+-
Mlib/ui/pages/share_handler_page.dart | 1-
Mlib/ui/pages/share_to_file_page.dart | 2+-
10 files changed, 211 insertions(+), 40 deletions(-)

diff --git a/lib/data/models/image_model.dart b/lib/data/models/image_model.dart @@ -8,7 +8,7 @@ class ImageModel { final List<String> tags; final String? analysisData; final DateTime createdAt; - final String status; // NEW: 'pending', 'analyzing', 'completed', 'failed' + final String status; // 'pending', 'analyzing', 'completed', 'failed' ImageModel({ required this.id, diff --git a/lib/data/repos/file_repo.dart b/lib/data/repos/file_repo.dart @@ -8,14 +8,14 @@ class FileRepo { await db.insert('files', file.toMap()); } - // New method: Gets all files, regardless of project ID, ordered by last_updated + // Gets all files, regardless of project ID, ordered by last_updated Future<List<FileModel>> getAllFiles() async { final db = await AppDatabase.db; final res = await db.query('files', orderBy: 'last_updated DESC'); return res.map((e) => FileModel.fromMap(e)).toList(); } - // New method: Gets the most recent files across all projects, capped by limit + // Gets the most recent files across all projects, capped by limit Future<List<FileModel>> getRecentFiles({int limit = 10}) async { final db = await AppDatabase.db; final res = await db.query( diff --git a/lib/services/file_service.dart b/lib/services/file_service.dart @@ -37,12 +37,11 @@ class FileService { return id; } - // New method to get ALL files Future<List<FileModel>> getAllFiles() async { return await _repo.getAllFiles(); } - // New method to get recent files globally (limit 3 for the UI) + // Get recent files globally (limit 10 for the UI) Future<List<FileModel>> getRecentFiles({int limit = 10}) async { return await _repo.getRecentFiles(limit: limit); } @@ -86,7 +85,5 @@ class FileService { Future<void> renameFile(String id, String newName) async { await _repo.updateDetails(id, name: newName); - } - - + } } diff --git a/lib/ui/pages/canvas_page.dart b/lib/ui/pages/canvas_page.dart @@ -319,7 +319,7 @@ class _CanvasPageState extends State<CanvasPage> { } // --- UNDO/REDO --- - // --- MAGIC DRAW UNDO/REDO (NEW) --- + // --- MAGIC DRAW UNDO/REDO --- void _recordMagicChange(List<DrawingPath> oldMagicPaths) { final newMagicPaths = List<DrawingPath>.from(_magicPaths); _magicDrawChangeStack.add( @@ -813,7 +813,7 @@ class _CanvasPageState extends State<CanvasPage> { } else { _paths = []; } - // [FIX] LOAD CANVAS DIMENSIONS & RESET VIEW INIT + // LOAD CANVAS DIMENSIONS & RESET VIEW INIT if (decoded['width'] != null && decoded['height'] != null) { _canvasSize = Size( (decoded['width'] as num).toDouble(), @@ -921,7 +921,7 @@ class _CanvasPageState extends State<CanvasPage> { context, ).showSnackBar(const SnackBar(content: Text('Coming soon'))); - // [UPDATED] New Bottom Sheet for Assets + // Bottom Sheet for Assets void _openStylesheet() { showModalBottomSheet( context: context, diff --git a/lib/ui/pages/home_page.dart b/lib/ui/pages/home_page.dart @@ -191,6 +191,130 @@ class _HomePageState extends State<HomePage> { } } + // Rename Project Logic + Future<void> _renameProject(ProjectModel project) async { + final controller = TextEditingController(text: project.title); + final theme = Theme.of(context); + + final didRename = await showDialog<bool>( + context: context, + builder: (ctx) => AlertDialog( + backgroundColor: theme.cardColor, + title: Text( + 'Rename Project', + style: TextStyle( + fontFamily: 'GeneralSans', + fontSize: 18, + color: theme.colorScheme.onSurface, + ), + ), + content: TextField( + controller: controller, + autofocus: true, + style: TextStyle(fontFamily: 'GeneralSans', color: theme.colorScheme.onSurface), + decoration: InputDecoration( + hintText: 'Enter new name', + hintStyle: TextStyle(color: theme.colorScheme.onSurface.withValues(alpha: 0.5)), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide(color: theme.colorScheme.primary.withValues(alpha: 0.5)), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide(color: theme.colorScheme.primary), + ), + ), + textCapitalization: TextCapitalization.sentences, + ), + actions: [ + TextButton( + child: Text('Cancel', style: TextStyle(color: theme.colorScheme.onSurface.withValues(alpha: 0.7))), + onPressed: () => Navigator.pop(ctx, false), + ), + TextButton( + child: Text('Save', style: TextStyle(color: theme.colorScheme.primary)), + onPressed: () { + if (controller.text.trim().isNotEmpty) { + Navigator.pop(ctx, true); + } + }, + ), + ], + ), + ); + + if (didRename == true && project.id != null) { + setState(() => _isLoading = true); + try { + await _projectService.updateProjectDetails(project.id!, title: controller.text.trim()); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Project renamed')), + ); + } + _loadData(); + } catch (e) { + if (mounted) { + setState(() => _isLoading = false); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error renaming: $e')), + ); + } + } + } + } + + // Delete Project Logic + Future<void> _deleteProject(ProjectModel project) async { + final theme = Theme.of(context); + final confirm = await showDialog<bool>( + context: context, + builder: (ctx) => AlertDialog( + backgroundColor: theme.cardColor, + title: Text( + 'Delete Project', + style: TextStyle( + fontFamily: 'GeneralSans', + fontSize: 18, + color: theme.colorScheme.onSurface, + ), + ), + content: Text( + 'Are you sure you want to delete "${project.title}"? This cannot be undone.', + style: TextStyle(fontFamily: 'GeneralSans', color: theme.colorScheme.onSurface.withValues(alpha: 0.8)), + ), + actions: [ + TextButton( + child: Text('Cancel', style: TextStyle(color: theme.colorScheme.onSurface.withValues(alpha: 0.7))), + onPressed: () => Navigator.pop(ctx, false), + ), + TextButton( + child: const Text('Delete', style: TextStyle(color: Colors.red)), + onPressed: () => Navigator.pop(ctx, true), + ), + ], + ), + ); + + if (confirm == true && project.id != null) { + setState(() => _isLoading = true); + try { + await _projectService.deleteProject(project.id!); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Project deleted successfully')), + ); + } + _loadData(); + } catch (e) { + if (mounted) { + setState(() => _isLoading = false); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error deleting project: $e')), + ); + } + } + } + } + Future<void> _openFile(FileModel file) async { double width = 1080; double height = 1920; @@ -247,6 +371,8 @@ class _HomePageState extends State<HomePage> { imageRepo: _imageRepo, onProjectTap: _openProject, onFileTap: _openFile, + onProjectDelete: _deleteProject, + onProjectRename: _renameProject, ), ), ).then((_) => _loadData()); @@ -446,6 +572,8 @@ class _HomePageState extends State<HomePage> { previewImages: _projectPreviews[p.id] ?? [], onTap: () => _openProject(p), + onRename: () => _renameProject(p), + onDelete: () => _deleteProject(p), isHorizontal: false, showGrid: false, ), @@ -568,6 +696,8 @@ class _HomePageState extends State<HomePage> { _projectPreviews[project.id] ?? [], onTap: () => _openProject(project), + onRename: () => _renameProject(project), + onDelete: () => _deleteProject(project), showGrid: false, ), ); @@ -700,7 +830,7 @@ class _HomePageState extends State<HomePage> { ]; return SizedBox( - height: 140, // Reduced + height: 140, child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: templates.length, @@ -709,7 +839,7 @@ class _HomePageState extends State<HomePage> { return GestureDetector( onTap: _showComingSoon, child: Container( - width: 100, // Reduced + width: 100, margin: const EdgeInsets.only(right: 12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -959,6 +1089,8 @@ class _ProjectCard extends StatelessWidget { final bool isDark; final List<String> previewImages; final VoidCallback onTap; + final VoidCallback? onRename; + final VoidCallback? onDelete; final bool isHorizontal; final bool showGrid; @@ -968,6 +1100,8 @@ class _ProjectCard extends StatelessWidget { required this.isDark, required this.previewImages, required this.onTap, + this.onRename, + this.onDelete, this.isHorizontal = true, this.showGrid = false, }); @@ -1045,10 +1179,49 @@ class _ProjectCard extends StatelessWidget { ), ), const SizedBox(width: 4), - Icon( - Icons.more_vert, - size: 16, - color: theme.colorScheme.onSurface.withValues(alpha: 0.6), + SizedBox( + height: 24, + width: 24, + child: PopupMenuButton<String>( + icon: Icon( + Icons.more_vert, + size: 16, + color: theme.colorScheme.onSurface.withValues(alpha: 0.6), + ), + padding: EdgeInsets.zero, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + onSelected: (value) { + if (value == 'rename' && onRename != null) { + onRename!(); + } else if (value == 'delete' && onDelete != null) { + onDelete!(); + } + }, + itemBuilder: (context) => [ + const PopupMenuItem( + value: 'rename', + child: Row( + children: [ + Icon(Icons.edit_outlined, size: 18), + SizedBox(width: 12), + Text('Rename', style: TextStyle(fontFamily: 'GeneralSans')), + ], + ), + ), + const PopupMenuItem( + value: 'delete', + child: Row( + children: [ + Icon(Icons.delete_outline, size: 18, color: Colors.red), + SizedBox(width: 12), + Text('Delete', style: TextStyle(fontFamily: 'GeneralSans', color: Colors.red)), + ], + ), + ), + ], + ), ), ], ), @@ -1131,6 +1304,8 @@ class _SeeAllPage extends StatefulWidget { final ImageRepo imageRepo; final Function(ProjectModel) onProjectTap; final Function(FileModel) onFileTap; + final Function(ProjectModel) onProjectRename; + final Function(ProjectModel) onProjectDelete; const _SeeAllPage({ required this.title, @@ -1140,6 +1315,8 @@ class _SeeAllPage extends StatefulWidget { required this.imageRepo, required this.onProjectTap, required this.onFileTap, + required this.onProjectRename, + required this.onProjectDelete, }); @override @@ -1291,8 +1468,17 @@ class _SeeAllPageState extends State<_SeeAllPage> { isDark: isDark, previewImages: _projectPreviews[project.id] ?? [], onTap: () => widget.onProjectTap(project), + // Chain callbacks, reload data on completion + onRename: () async { + await widget.onProjectRename(project); + _fetchData(); + }, + onDelete: () async { + await widget.onProjectDelete(project); + _fetchData(); + }, isHorizontal: false, - showGrid: true, // Show Grid for Projects here + showGrid: true, ); }, ) diff --git a/lib/ui/pages/image_details_page.dart b/lib/ui/pages/image_details_page.dart @@ -410,7 +410,7 @@ class _ImageDetailsPageState extends State<ImageDetailsPage> { }); } - // --- ADD NOTE INPUT DIALOG (FIXED KEYBOARD/MARGIN) --- + // --- ADD NOTE INPUT DIALOG --- void _showAddNoteInputDialog() { final TextEditingController newNoteController = TextEditingController(); // Default to the first tag or 'Compositions', ensure it exists in the list to prevent crashes @@ -738,7 +738,7 @@ class _ImageDetailsPageState extends State<ImageDetailsPage> { final isSelectionModeActive = _isDrawMode || _isResizing; return Scaffold( - // FIX: Prevents the main screen from pushing up when the keyboard opens + // Prevents the main screen from pushing up when the keyboard opens resizeToAvoidBottomInset: false, backgroundColor: Colors.white, appBar: AppBar( @@ -1412,14 +1412,14 @@ class NoteModalOverlay extends StatelessWidget { @override Widget build(BuildContext context) { - // Note: modalMinHeight is kept only for potential use with MaxHeight, but is not enforced as a minimum. + // Note: modalMinHeight is kept only for potential use with MaxHeight, but is not enforced as a minimum final mq = MediaQuery.of(context); final keyboardHeight = mq.viewInsets.bottom; final systemBottomPadding = mq.padding.bottom; return Align( alignment: Alignment.bottomCenter, - // FIX 1: Use AnimatedPadding on the outside to correctly handle keyboard elevation smoothly. + // Use AnimatedPadding on the outside to correctly handle keyboard elevation smoothly child: AnimatedPadding( duration: const Duration(milliseconds: 250), curve: Curves.easeOut, @@ -1427,13 +1427,10 @@ class NoteModalOverlay extends StatelessWidget { bottom: keyboardHeight, // Moves modal up to avoid keyboard ), child: ConstrainedBox( - // FIX 2: Removed minHeight constraint entirely. The Column inside uses mainAxisSize.min, - // allowing the dialog to shrink to fit content and preventing it from sitting "too high". constraints: BoxConstraints(maxHeight: screenSize.height), child: Material( - // Using Material to provide the background, border radius, and shadow. color: Colors.white, - elevation: 10, // Replicating the box shadow for visual style. + elevation: 10, shadowColor: Colors.black26, borderRadius: const BorderRadius.vertical(top: Radius.circular(20)), clipBehavior: Clip.antiAlias, diff --git a/lib/ui/pages/image_save_page.dart b/lib/ui/pages/image_save_page.dart @@ -87,8 +87,6 @@ class _ImageSavePageState extends State<ImageSavePage> { // Resizing state DragHandle _activeHandle = DragHandle.none; Offset? _startDragLocalOffset; // Used for moving the entire rect - - // FIXED: Store render size per image index final Map<int, Size> _imageRenderSizes = {}; final List<String> _availableTags = [ @@ -194,8 +192,6 @@ class _ImageSavePageState extends State<ImageSavePage> { final RenderBox? box = currentKey.currentContext?.findRenderObject() as RenderBox?; if (box == null) return null; - - // FIXED: Store the render size for this specific image _imageRenderSizes[_currentImageIndex] = box.size; // Convert global to local @@ -740,7 +736,6 @@ class _ImageSavePageState extends State<ImageSavePage> { final isPageLocked = _isDrawMode || _isResizing; return Scaffold( - // FIX: Prevents the main screen/image from pushing up when the keyboard opens. resizeToAvoidBottomInset: false, backgroundColor: Colors.white, appBar: AppBar( @@ -1221,9 +1216,9 @@ class _ImageSavePageState extends State<ImageSavePage> { } } -// ----------------------------------------------------------------------------- -// --- NEW HELPER CLASSES FOR CUSTOM HALF-PAGE MODAL OVERLAY --- -// ----------------------------------------------------------------------------- +// --------------------------------------------------------- +// --- HELPER CLASSES FOR CUSTOM HALF-PAGE MODAL OVERLAY --- +// --------------------------------------------------------- class NoteModalOverlay extends StatelessWidget { final Widget modalContent; @@ -1244,7 +1239,6 @@ class NoteModalOverlay extends StatelessWidget { return Align( alignment: Alignment.bottomCenter, - // FIX 1: Use AnimatedPadding for smooth keyboard elevation. child: AnimatedPadding( duration: const Duration(milliseconds: 250), curve: Curves.easeOut, @@ -1252,8 +1246,6 @@ class NoteModalOverlay extends StatelessWidget { bottom: keyboardHeight, // Moves modal up to avoid keyboard ), child: ConstrainedBox( - // FIX 2: Remove fixed minHeight to let the modal shrink to fit its content, - // addressing the "gets up too high" issue. constraints: BoxConstraints(maxHeight: screenSize.height), child: Material( // Using Material to provide the background, border radius, and shadow. diff --git a/lib/ui/pages/project_board_page.dart b/lib/ui/pages/project_board_page.dart @@ -315,7 +315,7 @@ class _ProjectBoardPageState extends State<ProjectBoardPage> { color: Variables.surfaceSubtle, border: Border.all(color: Variables.borderSubtle), ), - // FIX: Explicitly clip image to border radius + // Explicitly clip image to border radius child: ClipRRect( borderRadius: BorderRadius.circular(12), child: Stack( diff --git a/lib/ui/pages/share_handler_page.dart b/lib/ui/pages/share_handler_page.dart @@ -84,7 +84,6 @@ class _ShareHandlerPageState extends State<ShareHandlerPage> { MaterialPageRoute( builder: (_) => ShareToFilePage( - // FIX: Use 'sharedImage' instead of 'file' to match ShareToFilePage constructor sharedImage: tempFiles.first, ), ), diff --git a/lib/ui/pages/share_to_file_page.dart b/lib/ui/pages/share_to_file_page.dart @@ -244,7 +244,7 @@ class _ShareToFilePageState extends State<ShareToFilePage> { width: width, height: height, existingFile: file, - injectedMedia: widget.sharedImage, // 🔥 THIS IS THE FIX + injectedMedia: widget.sharedImage, ), ), );