Initial commit: imajViewer Flutter Linux image viewer

This commit is contained in:
Alhan committed 2026-07-22 03:59:14 +03:00
commit 8369ec2a5d
33 files changed
+2165

No files matched your search

+28
View File
@@ -0,0 +1,28 @@
import 'package:file_picker/file_picker.dart';
import 'image_manager.dart';
class FileHandler {
/// Open file picker dialog and load images
static Future<void> openFileDialog() async {
final result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['png', 'jpg', 'jpeg', 'webp', 'bmp', 'gif'],
allowMultiple: true,
);
if (result != null && result.files.isNotEmpty) {
final paths = result.files
.where((f) => f.path != null)
.map((f) => f.path!)
.toList();
ImageManager.instance.addImages(paths);
}
}
/// Load images from given file paths
static void loadImagesFromPaths(List<String> paths) {
if (paths.isNotEmpty) {
ImageManager.instance.addImages(paths);
}
}
}
+76
View File
@@ -0,0 +1,76 @@
import 'dart:io';
import 'package:flutter/material.dart';
class ImageManager extends ChangeNotifier {
static final ImageManager _instance = ImageManager._();
static ImageManager get instance => _instance;
ImageManager._();
final List<String> _images = [];
int _currentIndex = 0;
List<String> get images => List.unmodifiable(_images);
int get currentIndex => _currentIndex;
int get imageCount => _images.length;
String get currentImagePath {
if (_images.isEmpty) return '';
return _images[_currentIndex];
}
void addImages(List<String> paths) {
final validPaths = paths.where((p) {
final ext = p.toLowerCase().split('.').last;
const supported = ['png', 'jpg', 'jpeg', 'webp', 'bmp', 'gif'];
return supported.contains(ext) && File(p).existsSync();
}).toList();
if (validPaths.isEmpty) return;
_images.addAll(validPaths);
// Precache images
for (final path in validPaths) {
precacheImage(FileImage(File(path)), context!);
}
notifyListeners();
}
void setCurrentIndex(int index) {
if (index < 0 || index >= _images.length) return;
_currentIndex = index;
notifyListeners();
}
void nextImage() {
if (_images.isEmpty) return;
_currentIndex = (_currentIndex + 1) % _images.length;
notifyListeners();
}
void previousImage() {
if (_images.isEmpty) return;
_currentIndex = (_currentIndex - 1 + _images.length) % _images.length;
notifyListeners();
}
// Precache needs a BuildContext
BuildContext? context;
/// Configure image cache for high-res images
static void configureCache() {
PaintingBinding.instance.imageCache.maximumSize = 1000;
PaintingBinding.instance.imageCache.maximumSizeBytes = 1024 * 1024 * 1024; // 1 GB
}
set buildContext(BuildContext ctx) {
context = ctx;
}
void clear() {
_images.clear();
_currentIndex = 0;
notifyListeners();
}
}
+49
View File
@@ -0,0 +1,49 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
/// Persists and restores window position and size.
class WindowPersistence {
static File get _configFile {
final home = Platform.environment['HOME'] ?? '/tmp';
return File('$home/.config/imajviewer/window.json');
}
/// Save window rect synchronously (safe to call before destroy).
static void saveSync({
required Offset position,
required Size size,
}) {
final file = _configFile;
file.parent.createSync(recursive: true);
file.writeAsStringSync(jsonEncode({
'x': position.dx.toInt(),
'y': position.dy.toInt(),
'width': size.width.toInt(),
'height': size.height.toInt(),
}));
}
/// Restore saved window rect. Returns null if no saved config.
static ({Offset position, Size size})? restore() {
final file = _configFile;
if (!file.existsSync()) return null;
try {
final data = jsonDecode(file.readAsStringSync()) as Map<String, dynamic>;
final x = (data['x'] as num?)?.toDouble() ?? 0;
final y = (data['y'] as num?)?.toDouble() ?? 0;
final w = (data['width'] as num?)?.toDouble() ?? 1280;
final h = (data['height'] as num?)?.toDouble() ?? 800;
if (w < 200 || h < 200 || x < -1000 || y < -1000) return null;
return (
position: Offset(x, y),
size: Size(w, h),
);
} catch (_) {
return null;
}
}
}