feat: jfif/jpe/wbmp format destegi, pencere cascade yerlesimi, konum kaydi duzeltmesi

- file_handler/image_manager: jfif, jpe, wbmp uzantilari
- .desktop + install.sh MimeType: image/vnd.wap.wbmp eklendi
- main.dart: yeni pencere oncekinin saginda acilir (screen_retriever ile ekran tespiti)
- viewer_screen: 2sn polling kaldirildi, konum kaydi tek akisa oturtuldu (acilis + move/resize debounce + close senkron)
- dist/rebuild-deb.sh: .desktop kaynaktan kopyalanir (MimeType/StartupWMClass guncel kalir)
- .deb guncellendi, docs: session-2026-08-03 + plan dokumanlari
This commit is contained in:
Alhan
2026-08-03 06:44:57 +03:00
parent d09351feef
commit a65bdc152c
17 changed files with 272 additions and 38 deletions

View File

@@ -25,7 +25,11 @@ void main(List<String> args) async {
await windowManager.waitUntilReadyToShow(windowOptions, () async {
if (saved != null) {
await windowManager.setPosition(saved.position);
final position = await WindowPersistence.cascadePosition(
previousPosition: saved.position,
previousSize: saved.size,
);
await windowManager.setPosition(position);
await windowManager.setSize(saved.size);
} else {
await windowManager.center();
@@ -33,6 +37,11 @@ void main(List<String> args) async {
await windowManager.setResizable(true);
await windowManager.show();
await windowManager.focus();
// Açılış konumunu hemen kaydet — sıradaki pencere güncel referans okusun
final pos = await windowManager.getPosition();
final size = await windowManager.getSize();
WindowPersistence.saveSync(position: pos, size: size);
});
// Extract file paths from command-line arguments

View File

@@ -21,7 +21,6 @@ class _ViewerScreenState extends State<ViewerScreen> with WindowListener {
bool _hasImages = false;
bool _isDragging = false;
Timer? _saveDebounce;
Timer? _pollTimer;
Rect _lastBounds = Rect.zero;
@override
@@ -35,29 +34,6 @@ class _ViewerScreenState extends State<ViewerScreen> with WindowListener {
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
@@ -65,7 +41,6 @@ class _ViewerScreenState extends State<ViewerScreen> with WindowListener {
windowManager.removeListener(this);
ImageManager.instance.removeListener(_onImageManagerChanged);
_saveDebounce?.cancel();
_pollTimer?.cancel();
super.dispose();
}
@@ -74,10 +49,18 @@ class _ViewerScreenState extends State<ViewerScreen> with WindowListener {
void onWindowEvent(String eventName) {
switch (eventName) {
case 'close':
_doSave();
// 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((_) {});
_saveDebounce?.cancel();
_saveDebounce = Timer(const Duration(milliseconds: 300), () {
_doSave();

View File

@@ -6,7 +6,7 @@ class FileHandler {
static Future<void> openFileDialog() async {
final result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['png', 'jpg', 'jpeg', 'webp', 'bmp', 'gif'],
allowedExtensions: ['png', 'jpg', 'jpeg', 'jfif', 'jpe', 'webp', 'bmp', 'gif', 'wbmp'],
allowMultiple: true,
);

View File

@@ -21,7 +21,7 @@ class ImageManager extends ChangeNotifier {
void addImages(List<String> paths) {
final validPaths = paths.where((p) {
final ext = p.toLowerCase().split('.').last;
const supported = ['png', 'jpg', 'jpeg', 'webp', 'bmp', 'gif'];
const supported = ['png', 'jpg', 'jpeg', 'jfif', 'jpe', 'webp', 'bmp', 'gif', 'wbmp'];
return supported.contains(ext) && File(p).existsSync();
}).toList();

View File

@@ -1,6 +1,7 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:screen_retriever/screen_retriever.dart';
/// Persists and restores window position and size.
class WindowPersistence {
@@ -51,4 +52,38 @@ class WindowPersistence {
return null;
}
}
/// Cascade: place new window to the right of the previous one.
/// Falls to the next row (row start) when it would overflow the screen,
/// and wraps to the top-left if the next row also overflows.
static Future<Offset> cascadePosition({
required Offset previousPosition,
required Size previousSize,
}) async {
try {
final display = await screenRetriever.getPrimaryDisplay();
final workLeft = display.visiblePosition?.dx ?? 0;
final workTop = display.visiblePosition?.dy ?? 0;
final workWidth = display.visibleSize?.width ?? display.size.width;
final workHeight = display.visibleSize?.height ?? display.size.height;
var x = previousPosition.dx + previousSize.width;
var y = previousPosition.dy;
if (x + previousSize.width > workLeft + workWidth) {
x = workLeft;
y = previousPosition.dy + previousSize.height;
}
if (y + previousSize.height > workTop + workHeight) {
x = workLeft;
y = workTop;
}
return Offset(x, y);
} catch (_) {
// Fallback: keep previous position if screen info is unavailable.
return previousPosition;
}
}
}