creek

The AI Image Editor of 2030
commit b764b8571395a7ffaa09baa856f272f4b20a104a
parent 90bbbe9f83839900c41a582d4b5baa65384e76df
Author: maydayv7 <maydayv7@gmail.com>
Date:   Mon,  1 Dec 2025 00:20:52 +0530

Fix image details page header

Diffstat:
Mlib/ui/pages/image_details_page.dart | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Mlib/ui/widgets/image_context_menu.dart | 92++++++++++++++-----------------------------------------------------------------
Alib/utils/image_actions_helper.dart | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 209 insertions(+), 88 deletions(-)

diff --git a/lib/ui/pages/image_details_page.dart b/lib/ui/pages/image_details_page.dart @@ -2,10 +2,12 @@ import 'dart:io'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import '../../services/image_service.dart'; import '../../services/note_service.dart'; import '../../data/models/note_model.dart'; import '../../data/models/image_model.dart'; +import '../../utils/image_actions_helper.dart'; // --- STATE MACHINE FOR SELECTION MODE --- enum DragHandle { @@ -749,16 +751,55 @@ class _ImageDetailsPageState extends State<ImageDetailsPage> { ), onPressed: () => Navigator.pop(context), ), - title: Text( - _imageModel?.name ?? "Image Details", - style: const TextStyle( - color: Colors.black, - fontSize: 18, - fontWeight: FontWeight.w600, - ), - ), + centerTitle: true, + title: + (!isSelectionModeActive && _imageModel != null) + ? Container( + height: 40, + decoration: BoxDecoration( + color: const Color( + 0xFFF3F4F6, + ), + borderRadius: BorderRadius.circular(30), + ), + child: InkWell( + borderRadius: BorderRadius.circular(30), + onTap: + () => ImageActionsHelper.sendToFiles( + context, + _imageModel!.filePath, + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset( + 'assets/icons/files_icon.svg', + width: 18, + height: 18, + colorFilter: const ColorFilter.mode( + Colors.black, + BlendMode.srcIn, + ), + ), + const SizedBox(width: 8), + const Text( + "Send to File", + style: TextStyle( + color: Colors.black, + fontWeight: FontWeight.w600, + fontSize: 14, + ), + ), + ], + ), + ), + ), + ) + : null, actions: [ - // CONFIRM SELECTION BUTTON (Visible only in resizing mode) + // 1. CONFIRM SELECTION (Resizing mode) if (_isResizing && _finalSelectionRect != null) IconButton( icon: const Icon( @@ -767,12 +808,48 @@ class _ImageDetailsPageState extends State<ImageDetailsPage> { ), onPressed: _confirmSelectionAndShowModal, ), - // CANCEL SELECTION BUTTON (Visible only in drawing/resizing mode) + + // 2. CANCEL SELECTION (Any selection mode) if (isSelectionModeActive) IconButton( icon: const Icon(Icons.close, color: Colors.black), onPressed: _resetSelectionMode, ), + + // 3. STANDARD ACTIONS (when NOT selecting) + if (!isSelectionModeActive && _imageModel != null) ...[ + // Share Button + IconButton( + onPressed: + () => ImageActionsHelper.shareImage( + context, + _imageModel!.filePath, + ), + icon: SvgPicture.asset( + 'assets/icons/share_icon.svg', + width: 20, + height: 20, + colorFilter: const ColorFilter.mode( + Colors.black, + BlendMode.srcIn, + ), + ), + ), + + // Rename Button + IconButton( + onPressed: + () => ImageActionsHelper.renameImage( + context, + _imageModel!, + () => _loadData(), // Refresh title after rename + ), + icon: const Icon( + Icons.drive_file_rename_outline, + color: Colors.black, + ), + ), + ], ], ), body: @@ -1151,7 +1228,7 @@ class _ImageDetailsPageState extends State<ImageDetailsPage> { } } -// --- NOTES LIST SHEET (Remains unchanged as it uses DraggableScrollableSheet) --- +// --- NOTES LIST SHEET --- class _NotesListSheet extends StatefulWidget { final List<NoteModel> notes; final int? highlightId; @@ -1323,7 +1400,7 @@ class __NotesListSheetState extends State<_NotesListSheet> { } } -// --- REUSABLE WIDGETS FOR MODAL OVERLAY (Keyboard Fix and Shadow Removal) --- +// --- REUSABLE WIDGETS FOR MODAL OVERLAY --- class NoteModalOverlay extends StatelessWidget { final Widget modalContent; diff --git a/lib/ui/widgets/image_context_menu.dart b/lib/ui/widgets/image_context_menu.dart @@ -2,10 +2,9 @@ import 'dart:io'; import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:share_plus/share_plus.dart'; import '../../data/models/image_model.dart'; import '../../services/image_service.dart'; -import '../pages/share_to_file_page.dart'; +import '../../utils/image_actions_helper.dart'; import '../styles/variables.dart'; class ImageContextMenu extends StatelessWidget { @@ -104,101 +103,42 @@ class _RadialMenuOverlayState extends State<_RadialMenuOverlay> with SingleTicke }); } - // --- ACTIONS --- + // ACTIONS Future<void> _shareImage() async { Navigator.of(context).pop(); - final file = File(widget.image.filePath); - if (await file.exists()) { - await Share.shareXFiles([XFile(widget.image.filePath)]); - } + await ImageActionsHelper.shareImage(context, widget.image.filePath); } void _sendToFiles() { Navigator.of(context).pop(); - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => ShareToFilePage(sharedImage: File(widget.image.filePath)), - ), - ); + ImageActionsHelper.sendToFiles(context, widget.image.filePath); } Future<void> _renameImage() async { // Hide buttons to not obstruct dialog _controller.reverse(); - final TextEditingController nameController = TextEditingController(text: widget.image.name); - await showDialog( - context: context, - builder: (ctx) => AlertDialog( - backgroundColor: Colors.white, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), - title: const Text("Rename Image", style: TextStyle(fontFamily: 'GeneralSans', fontWeight: FontWeight.w600)), - content: TextField( - controller: nameController, - autofocus: true, - decoration: InputDecoration( - hintText: "Enter new name", - filled: true, - fillColor: Variables.surfaceSubtle, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide.none, - ), - ), - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(ctx), - child: const Text("Cancel", style: TextStyle(color: Variables.textSecondary, fontFamily: 'GeneralSans')), - ), - TextButton( - onPressed: () async { - if (nameController.text.isNotEmpty) { - await ImageService().renameImage(widget.image.id, nameController.text.trim()); - widget.onImageDeleted(); // Trigger Refresh - Navigator.pop(ctx); - } - }, - child: const Text("Save", style: TextStyle(color: Variables.textPrimary, fontFamily: 'GeneralSans', fontWeight: FontWeight.w600)), - ), - ], - ), + await ImageActionsHelper.renameImage( + context, + widget.image, + widget.onImageDeleted, // Triggers refresh in parent ); if (mounted) Navigator.of(context).pop(); } Future<void> _deleteImage() async { - final confirm = await showDialog<bool>( - context: context, - builder: (ctx) => AlertDialog( - backgroundColor: Colors.white, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), - title: const Text("Delete Image?", style: TextStyle(fontFamily: 'GeneralSans', fontWeight: FontWeight.w600)), - content: const Text( - "This action cannot be undone.", - style: TextStyle(fontFamily: 'GeneralSans', color: Variables.textSecondary), - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(ctx, false), - child: const Text("Cancel", style: TextStyle(color: Variables.textSecondary, fontFamily: 'GeneralSans')), - ), - TextButton( - onPressed: () => Navigator.pop(ctx, true), - child: const Text("Delete", style: TextStyle(color: Colors.red, fontFamily: 'GeneralSans', fontWeight: FontWeight.w600)), - ), - ], - ), + // Hide buttons + _controller.reverse(); + + await ImageActionsHelper.deleteImage( + context, + widget.image, + widget.onImageDeleted, ); - if (confirm == true) { - await ImageService().deleteImage(widget.image.id); - widget.onImageDeleted(); - if (mounted) Navigator.of(context).pop(); - } + if (mounted) Navigator.of(context).pop(); } void _notImplemented() { diff --git a/lib/utils/image_actions_helper.dart b/lib/utils/image_actions_helper.dart @@ -0,0 +1,104 @@ +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:share_plus/share_plus.dart'; +import '../data/models/image_model.dart'; +import '../services/image_service.dart'; +import '../ui/pages/share_to_file_page.dart'; +import '../ui/styles/variables.dart'; + +class ImageActionsHelper { + static Future<void> shareImage(BuildContext context, String filePath) async { + final file = File(filePath); + if (await file.exists()) { + await Share.shareXFiles([XFile(filePath)]); + } + } + + static void sendToFiles(BuildContext context, String filePath) { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => ShareToFilePage(sharedImage: File(filePath)), + ), + ); + } + + static Future<void> renameImage( + BuildContext context, + ImageModel image, + VoidCallback onSuccess, + ) async { + final TextEditingController nameController = TextEditingController(text: image.name); + await showDialog( + context: context, + builder: (ctx) => AlertDialog( + backgroundColor: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + title: const Text("Rename Image", style: TextStyle(fontFamily: 'GeneralSans', fontWeight: FontWeight.w600)), + content: TextField( + controller: nameController, + autofocus: true, + decoration: InputDecoration( + hintText: "Enter new name", + filled: true, + fillColor: Variables.surfaceSubtle, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx), + child: const Text("Cancel", style: TextStyle(color: Variables.textSecondary, fontFamily: 'GeneralSans')), + ), + TextButton( + onPressed: () async { + if (nameController.text.isNotEmpty) { + await ImageService().renameImage(image.id, nameController.text.trim()); + onSuccess(); + Navigator.pop(ctx); + } + }, + child: const Text("Save", style: TextStyle(color: Variables.textPrimary, fontFamily: 'GeneralSans', fontWeight: FontWeight.w600)), + ), + ], + ), + ); + } + + static Future<void> deleteImage( + BuildContext context, + ImageModel image, + VoidCallback onSuccess, + ) async { + final confirm = await showDialog<bool>( + context: context, + builder: (ctx) => AlertDialog( + backgroundColor: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + title: const Text("Delete Image?", style: TextStyle(fontFamily: 'GeneralSans', fontWeight: FontWeight.w600)), + content: const Text( + "This action cannot be undone.", + style: TextStyle(fontFamily: 'GeneralSans', color: Variables.textSecondary), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx, false), + child: const Text("Cancel", style: TextStyle(color: Variables.textSecondary, fontFamily: 'GeneralSans')), + ), + TextButton( + onPressed: () => Navigator.pop(ctx, true), + child: const Text("Delete", style: TextStyle(color: Colors.red, fontFamily: 'GeneralSans', fontWeight: FontWeight.w600)), + ), + ], + ), + ); + + if (confirm == true) { + await ImageService().deleteImage(image.id); + onSuccess(); + } + } +}