import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:window_manager/window_manager.dart'; import 'package:desktop_drop/desktop_drop.dart'; import 'package:pdf/pdf.dart'; import 'package:pdf/widgets.dart' as pw; import 'package:printing/printing.dart'; import '../services/image_manager.dart'; import '../services/file_handler.dart'; import '../widgets/custom_title_bar.dart'; import '../widgets/image_canvas.dart'; import '../widgets/window_resize_zones.dart'; import '../shortcuts/app_shortcuts.dart'; import '../services/window_persistence.dart'; class ViewerScreen extends StatefulWidget { final List initialFiles; const ViewerScreen({super.key, this.initialFiles = const []}); @override State createState() => _ViewerScreenState(); } class _ViewerScreenState extends State with WindowListener { bool _hasImages = false; bool _isDragging = false; bool _controlsVisible = false; Timer? _saveDebounce; Rect _lastBounds = Rect.zero; @override void initState() { super.initState(); windowManager.addListener(this); ImageManager.instance.addListener(_onImageManagerChanged); if (widget.initialFiles.isNotEmpty) { WidgetsBinding.instance.addPostFrameCallback((_) { FileHandler.loadImagesFromPaths(widget.initialFiles); }); } } @override void dispose() { windowManager.removeListener(this); ImageManager.instance.removeListener(_onImageManagerChanged); _saveDebounce?.cancel(); super.dispose(); } /// Universal event handler @override void onWindowEvent(String eventName) { switch (eventName) { case 'close': // Senkron kayıt — async çağrı kapanışta tamamlanmayabilir if (_lastBounds != Rect.zero) { WindowPersistence.saveSync( position: _lastBounds.topLeft, size: _lastBounds.size, ); } break; case 'resize': case 'move': // Son bilinen konumu hemen güncelle (kapanış senkron kaydı için) windowManager.getBounds().then((b) => _lastBounds = b).catchError((_) => _lastBounds); _saveDebounce?.cancel(); _saveDebounce = Timer(const Duration(milliseconds: 300), () { _doSave(); }); break; } } void _onImageManagerChanged() { setState(() => _hasImages = ImageManager.instance.images.isNotEmpty); } Future _doSave() async { try { final pos = await windowManager.getPosition(); final size = await windowManager.getSize(); _lastBounds = Rect.fromLTWH(pos.dx, pos.dy, size.width, size.height); WindowPersistence.saveSync(position: pos, size: size); } catch (_) { // Silently ignore save errors } } void _showSettings() { showDialog( context: context, builder: (context) => StatefulBuilder( builder: (context, setDialogState) => AlertDialog( backgroundColor: const Color(0xFF2a2a2a), title: const Text('Ayarlar', style: TextStyle(color: Colors.white, fontSize: 18)), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Sıralama', style: TextStyle(color: Colors.white70, fontSize: 13)), RadioListTile( value: false, groupValue: ImageManager.instance.sortByName, dense: true, activeColor: Colors.blueAccent, title: const Text('Zamana göre', style: TextStyle(color: Colors.white, fontSize: 14)), onChanged: (v) { ImageManager.instance.setSortByName(v!); setDialogState(() {}); }, ), RadioListTile( value: true, groupValue: ImageManager.instance.sortByName, dense: true, activeColor: Colors.blueAccent, title: const Text('İsme göre', style: TextStyle(color: Colors.white, fontSize: 14)), onChanged: (v) { ImageManager.instance.setSortByName(v!); setDialogState(() {}); }, ), const Divider(color: Colors.white24, height: 24), const Text('imajViewer', style: TextStyle(color: Colors.white, fontSize: 15)), const SizedBox(height: 4), const Text( 'Ultra hafif ve hızlı resim görüntüleyici. ' 'Görselleri açın, gezinti için okları veya klavye tuşlarını kullanın.', style: TextStyle(color: Colors.white70, fontSize: 13, height: 1.5), ), const SizedBox(height: 12), const Text('Kısayollar', style: TextStyle(color: Colors.white70, fontSize: 13)), const SizedBox(height: 4), const Text( '• Sol/Sağ ok — önceki/sonraki resim\n' '• Ctrl+Q — pencereyi kapat\n' '• Ctrl+Shift+Q — tüm pencereleri kapat\n' '• Çift tık — doldur/sığdır geçişi\n' '• Sağ tık sürükle — kontrast\n' '• Shift+sağ tık — döndür\n' '• Ctrl+sağ tık — doygunluk\n' '• Ctrl+Shift+sağ tık — parlaklık', style: TextStyle(color: Colors.white70, fontSize: 13, height: 1.6), ), ], ), ), actions: [ TextButton.icon( onPressed: () { Navigator.pop(context); _printImage(); }, icon: const Icon(Icons.print, color: Colors.white70, size: 18), label: const Text('Yazdır', style: TextStyle(color: Colors.white70)), ), TextButton( onPressed: () => Navigator.pop(context), child: const Text('Kapat', style: TextStyle(color: Colors.white70)), ), ], ), ), ); } Future _printImage() async { final path = ImageManager.instance.currentImagePath; if (path.isEmpty) return; final bytes = await File(path).readAsBytes(); final image = pw.MemoryImage(bytes); await Printing.layoutPdf( onLayout: (format) async { final doc = pw.Document(); doc.addPage( pw.Page( pageFormat: PdfPageFormat.a4, build: (context) => pw.Center( child: pw.Image(image, fit: pw.BoxFit.contain), ), ), ); return doc.save(); }, ); } @override Widget build(BuildContext context) { return AppShortcuts( child: Scaffold( backgroundColor: Colors.transparent, body: WindowResizeZones( child: DropTarget( onDragEntered: (_) => setState(() => _isDragging = true), onDragExited: (_) => setState(() => _isDragging = false), onDragDone: (details) { setState(() => _isDragging = false); FileHandler.loadImagesFromPaths( details.files.map((f) => f.path).toList()); }, child: MouseRegion( onEnter: (_) => setState(() => _controlsVisible = true), onExit: (_) => setState(() => _controlsVisible = false), child: Stack( children: [ Positioned( top: 0, left: 0, right: 0, bottom: 0, child: Container(color: const Color(0xFF1a1a1a)), ), Positioned( top: 0, left: 0, right: 0, bottom: 0, child: Container( color: Colors.transparent, child: _hasImages ? ImageCanvas( filePath: ImageManager.instance.currentImagePath, ) : const Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.image, size: 64, color: Colors.white24), SizedBox(height: 16), Text('Resimleri buraya sürükleyin\nveya Ctrl+O ile açın', textAlign: TextAlign.center, style: TextStyle(color: Colors.white38, fontSize: 16)), ], ), ), ), ), CustomTitleBar( onSettings: _showSettings, visible: _controlsVisible, ), if (_hasImages && _controlsVisible) ...[ Positioned( left: 8, bottom: 8, child: _NavButton( icon: Icons.chevron_left, onTap: () => ImageManager.instance.previousImage(), ), ), Positioned( right: 8, bottom: 8, child: _NavButton( icon: Icons.chevron_right, onTap: () => ImageManager.instance.nextImage(), ), ), ], if (_isDragging) Positioned.fill( child: Container( color: Colors.blue.withValues(alpha: 0.1), child: Center( child: Container( padding: const EdgeInsets.all(32), decoration: BoxDecoration( color: Colors.black87, borderRadius: BorderRadius.circular(16), border: Border.all(color: Colors.blueAccent, width: 2), ), child: const Text('Bırakın', style: TextStyle(color: Colors.white, fontSize: 24)), ), ), ), ), ], ), ), ), ), ), ); } } class _NavButton extends StatefulWidget { final IconData icon; final VoidCallback onTap; const _NavButton({required this.icon, required this.onTap}); @override State<_NavButton> createState() => _NavButtonState(); } class _NavButtonState extends State<_NavButton> { bool _hovered = false; @override Widget build(BuildContext context) { return GestureDetector( onTap: widget.onTap, child: MouseRegion( cursor: SystemMouseCursors.click, onEnter: (_) => setState(() => _hovered = true), onExit: (_) => setState(() => _hovered = false), child: Container( width: 36, height: 36, decoration: BoxDecoration( color: _hovered ? Colors.red.withValues(alpha: 0.8) : Colors.black.withValues(alpha: 0.35), shape: BoxShape.circle, ), child: Icon(widget.icon, color: Colors.white, size: 22), ), ), ); } }