commit b764b8571395a7ffaa09baa856f272f4b20a104a
parent 90bbbe9f83839900c41a582d4b5baa65384e76df
Author: maydayv7 <maydayv7@gmail.com>
Date: Mon, 1 Dec 2025 00:20:52 +0530
Fix image details page header
Diffstat:
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();
+ }
+ }
+}