Files
imajviewer/lib/screens/viewer_screen.dart
T
Alhan 4dae4234ba
Build Windows / build-windows (push) Has been cancelled
feat: letterbox-aware clamp, full-frame image, fit/fill zoom fix
- image_canvas.dart:
  - Fill mod: viewport tabanli clamp (_tx=0'da kilit)
  - Fit mod: RenderBox ile resmin gercek render boyutuna gore clamp
  - Letterbox offset (offX/offY) hesaba katilarak resmin kenarlarina kadar pan
  - Zoom-to-cursor dogru calisiyor (alignment: topLeft ile)
  - Min scale 1.0 (her iki modda da)
  - Overflow durumunda lo/hi reversed clamp (goruntu disari kacmaz)
- viewer_screen.dart: image alani top:24 -> top:0 (tam cerceve)
- install.sh: release -> debug bundle path (gelistirme)
2026-07-24 06:22:59 +03:00

180 lines
5.7 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:window_manager/window_manager.dart';
import 'package:desktop_drop/desktop_drop.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<String> initialFiles;
const ViewerScreen({super.key, this.initialFiles = const []});
@override
State<ViewerScreen> createState() => _ViewerScreenState();
}
class _ViewerScreenState extends State<ViewerScreen> with WindowListener {
bool _hasImages = false;
bool _isDragging = false;
Timer? _saveDebounce;
Timer? _pollTimer;
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);
});
}
// İlk kayıt + periyodik polling başlat
WidgetsBinding.instance.addPostFrameCallback((_) async {
await Future.delayed(const Duration(milliseconds: 200));
await _doSave();
_startPolling();
});
}
void _startPolling() {
_pollTimer?.cancel();
_pollTimer = Timer.periodic(const Duration(seconds: 2), (_) async {
try {
final bounds = await windowManager.getBounds();
if (bounds != _lastBounds) {
_lastBounds = bounds;
WindowPersistence.saveSync(
position: bounds.topLeft,
size: bounds.size,
);
}
} catch (_) {}
});
}
@override
void dispose() {
windowManager.removeListener(this);
ImageManager.instance.removeListener(_onImageManagerChanged);
_saveDebounce?.cancel();
_pollTimer?.cancel();
super.dispose();
}
/// Universal event handler
@override
void onWindowEvent(String eventName) {
switch (eventName) {
case 'close':
_doSave();
break;
case 'resize':
case 'move':
_saveDebounce?.cancel();
_saveDebounce = Timer(const Duration(milliseconds: 300), () {
_doSave();
});
break;
}
}
void _onImageManagerChanged() {
setState(() => _hasImages = ImageManager.instance.images.isNotEmpty);
}
Future<void> _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
}
}
@override
Widget build(BuildContext context) {
ImageManager.instance.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: 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)),
],
),
),
),
),
const CustomTitleBar(),
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)),
),
),
),
),
],
),
),
),
),
);
}
}