feat: gezinme oklari (hover), ayarlar penceresi (zaman/isme gore siralama), yazdirma (printing paketi), title hover saydamligi; .deb guncellendi

This commit is contained in:
Alhan
2026-08-06 03:43:48 +03:00
parent 185653f34e
commit f3a5aeb604
12 changed files with 431 additions and 38 deletions

View File

@@ -1,7 +1,11 @@
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';
@@ -20,6 +24,7 @@ class ViewerScreen extends StatefulWidget {
class _ViewerScreenState extends State<ViewerScreen> with WindowListener {
bool _hasImages = false;
bool _isDragging = false;
bool _controlsVisible = false;
Timer? _saveDebounce;
Rect _lastBounds = Rect.zero;
@@ -84,6 +89,112 @@ class _ViewerScreenState extends State<ViewerScreen> with WindowListener {
}
}
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<bool>(
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<bool>(
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+O — dosya aç\n'
'• Ctrl+Q — pencereyi 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<void> _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(
@@ -98,41 +209,65 @@ class _ViewerScreenState extends State<ViewerScreen> with WindowListener {
FileHandler.loadImagesFromPaths(
details.files.map((f) => f.path).toList());
},
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)),
],
),
),
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)),
),
),
const CustomTitleBar(),
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(
@@ -152,6 +287,7 @@ class _ViewerScreenState extends State<ViewerScreen> with WindowListener {
),
),
],
),
),
),
),
@@ -159,3 +295,40 @@ class _ViewerScreenState extends State<ViewerScreen> with WindowListener {
);
}
}
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),
),
),
);
}
}

View File

@@ -12,10 +12,12 @@ class ImageManager extends ChangeNotifier {
final List<String> _images = [];
int _currentIndex = 0;
bool _sortByName = false;
List<String> get images => List.unmodifiable(_images);
int get currentIndex => _currentIndex;
int get imageCount => _images.length;
bool get sortByName => _sortByName;
String get currentImagePath {
if (_images.isEmpty) return '';
@@ -48,6 +50,9 @@ class ImageManager extends ChangeNotifier {
} catch (_) {}
}
entries.sort((a, b) {
if (_sortByName) {
return a.$1.toLowerCase().compareTo(b.$1.toLowerCase());
}
final byDate = b.$2.compareTo(a.$2);
return byDate != 0 ? byDate : a.$1.compareTo(b.$1);
});
@@ -69,6 +74,16 @@ class ImageManager extends ChangeNotifier {
notifyListeners();
}
void setSortByName(bool value) {
if (_sortByName == value) return;
_sortByName = value;
if (_images.isNotEmpty) {
setGalleryFromPaths([_images[_currentIndex]]);
} else {
notifyListeners();
}
}
void setCurrentIndex(int index) {
if (index < 0 || index >= _images.length) return;
_currentIndex = index;

View File

@@ -2,7 +2,10 @@ import 'package:flutter/material.dart';
import 'package:window_manager/window_manager.dart';
class CustomTitleBar extends StatelessWidget {
const CustomTitleBar({super.key});
final VoidCallback? onSettings;
final bool visible;
const CustomTitleBar({super.key, this.onSettings, this.visible = true});
@override
Widget build(BuildContext context) {
@@ -10,11 +13,29 @@ class CustomTitleBar extends StatelessWidget {
top: 0,
left: 0,
right: 0,
child: DragToMoveArea(
child: AnimatedOpacity(
opacity: visible ? 1.0 : 0.0,
duration: const Duration(milliseconds: 150),
child: DragToMoveArea(
child: SizedBox(
height: 24,
child: Stack(
children: [
if (onSettings != null)
Positioned(
left: 0,
top: 0,
bottom: 0,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
_BarButton(
icon: Icons.settings,
onTap: onSettings!,
),
],
),
),
Positioned(
right: 0,
top: 0,
@@ -36,6 +57,7 @@ class CustomTitleBar extends StatelessWidget {
],
),
),
),
),
);
}
@@ -45,7 +67,10 @@ class _BarButton extends StatefulWidget {
final IconData icon;
final VoidCallback onTap;
const _BarButton({required this.icon, required this.onTap});
const _BarButton({
required this.icon,
required this.onTap,
});
@override
State<_BarButton> createState() => _BarButtonState();