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:
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,
),
),
);