- _imageSize reset on image switch (wrong swap threshold on nav) - downsample caps both dims (tall images no longer full-res decode) - single _readImageSize instead of leaking loadingBuilder listeners - evict old downsample provider on full-res swap - remove imageCache.clear() that dropped live image (gallery flash)
115 lines
3.1 KiB
Dart
115 lines
3.1 KiB
Dart
import 'dart:io';
|
||
import 'package:flutter/material.dart';
|
||
|
||
class ImageManager extends ChangeNotifier {
|
||
static final ImageManager _instance = ImageManager._();
|
||
static ImageManager get instance => _instance;
|
||
ImageManager._();
|
||
|
||
static const _supportedExtensions = [
|
||
'png', 'jpg', 'jpeg', 'jfif', 'jpe', 'webp', 'bmp', 'gif', 'wbmp',
|
||
];
|
||
|
||
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 '';
|
||
return _images[_currentIndex];
|
||
}
|
||
|
||
/// Açılan resmin bulunduğu klasörü galeri olarak kurar (en yeni önce).
|
||
void setGalleryFromPaths(List<String> paths) {
|
||
String? anchor;
|
||
for (final p in paths) {
|
||
final ext = p.toLowerCase().split('.').last;
|
||
if (_supportedExtensions.contains(ext) && File(p).existsSync()) {
|
||
anchor = p;
|
||
break;
|
||
}
|
||
}
|
||
if (anchor == null) return;
|
||
|
||
final dir = File(anchor).parent;
|
||
if (!dir.existsSync()) return;
|
||
|
||
final List<(String, DateTime)> entries = [];
|
||
for (final entity in dir.listSync()) {
|
||
if (entity is! File) continue;
|
||
final p = entity.path;
|
||
final ext = p.toLowerCase().split('.').last;
|
||
if (!_supportedExtensions.contains(ext)) continue;
|
||
try {
|
||
entries.add((p, entity.lastModifiedSync()));
|
||
} 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);
|
||
});
|
||
|
||
_images
|
||
..clear()
|
||
..addAll(entries.map((e) => e.$1));
|
||
|
||
_currentIndex = _images.indexOf(anchor);
|
||
if (_currentIndex < 0) {
|
||
final base = anchor.split(Platform.pathSeparator).last;
|
||
_currentIndex = _images
|
||
.indexWhere((p) => p.split(Platform.pathSeparator).last == base);
|
||
}
|
||
if (_currentIndex < 0) _currentIndex = 0;
|
||
|
||
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;
|
||
notifyListeners();
|
||
}
|
||
|
||
void previousImage() {
|
||
if (_images.isEmpty) return;
|
||
_currentIndex = (_currentIndex - 1 + _images.length) % _images.length;
|
||
notifyListeners();
|
||
}
|
||
|
||
void nextImage() {
|
||
if (_images.isEmpty) return;
|
||
_currentIndex = (_currentIndex + 1) % _images.length;
|
||
notifyListeners();
|
||
}
|
||
|
||
/// Configure image cache for high-res images
|
||
static void configureCache() {
|
||
PaintingBinding.instance.imageCache.maximumSize = 16;
|
||
PaintingBinding.instance.imageCache.maximumSizeBytes = 256 * 1024 * 1024; // 256 MB
|
||
}
|
||
|
||
void clear() {
|
||
_images.clear();
|
||
_currentIndex = 0;
|
||
notifyListeners();
|
||
}
|
||
}
|