Files
1356/lifetimer/lib/data/services/image_cache_service.dart
T
Tomas Dvorak 37ffb93923 feat: Complete Phase 1 - Full Flutter app implementation with comprehensive features
Version: 1.1.0

Major changes:
- Implemented complete Flutter app structure with all core features
- Added comprehensive UI screens for auth, countdown, goals, profile, settings, and social features
- Integrated Supabase backend with authentication and data repositories
- Added offline support with Hive caching and local storage
- Implemented comprehensive routing with go_router
- Added location services with Google Maps integration
- Implemented notifications and home widget support
- Added voice recording capabilities and AI chat features
- Created comprehensive test suite and documentation
- Added Android and iOS platform configurations
- Implemented achievements system and social features
- Added calendar integration and bucket list functionality

This represents a complete Phase 1 milestone with 3,775 additions across 31 files.
2026-01-04 14:33:54 +01:00

162 lines
3.9 KiB
Dart

// ignore_for_file: depend_on_referenced_packages
import 'dart:io';
import 'dart:typed_data';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as path;
import 'package:crypto/crypto.dart';
import 'dart:convert';
class ImageCacheService {
static const int _maxCacheSize = 50 * 1024 * 1024; // 50MB
static const Duration _cacheExpiry = Duration(days: 30);
static const int _maxConcurrentOperations = 3;
late Directory _cacheDir;
bool _initialized = false;
int _activeOperations = 0;
Future<void> init() async {
if (_initialized) return;
final appDir = await getApplicationDocumentsDirectory();
_cacheDir = Directory(path.join(appDir.path, 'image_cache'));
if (!await _cacheDir.exists()) {
await _cacheDir.create(recursive: true);
}
_initialized = true;
await _cleanupExpiredCache();
}
String _generateCacheKey(String url) {
final bytes = utf8.encode(url);
final digest = sha256.convert(bytes);
return digest.toString();
}
Future<File?> getCachedImage(String url) async {
if (!_initialized) await init();
final cacheKey = _generateCacheKey(url);
final cachedFile = File(path.join(_cacheDir.path, '$cacheKey.jpg'));
if (!await cachedFile.exists()) {
return null;
}
final stat = await cachedFile.stat();
final age = DateTime.now().difference(stat.modified);
if (age > _cacheExpiry) {
await cachedFile.delete();
return null;
}
return cachedFile;
}
Future<File> cacheImage(String url, Uint8List imageData) async {
if (!_initialized) await init();
// Limit concurrent operations to avoid overwhelming the system
while (_activeOperations >= _maxConcurrentOperations) {
await Future.delayed(const Duration(milliseconds: 10));
}
_activeOperations++;
try {
final cacheKey = _generateCacheKey(url);
final cachedFile = File(path.join(_cacheDir.path, '$cacheKey.jpg'));
await cachedFile.writeAsBytes(imageData);
await _enforceCacheSizeLimit();
return cachedFile;
} finally {
_activeOperations--;
}
}
Future<void> clearCache() async {
if (!_initialized) await init();
if (await _cacheDir.exists()) {
await _cacheDir.delete(recursive: true);
await _cacheDir.create(recursive: true);
}
}
Future<int> getCacheSize() async {
if (!_initialized) await init();
int totalSize = 0;
if (await _cacheDir.exists()) {
await for (final entity in _cacheDir.list()) {
if (entity is File) {
totalSize += await entity.length();
}
}
}
return totalSize;
}
Future<void> _cleanupExpiredCache() async {
if (!await _cacheDir.exists()) return;
final now = DateTime.now();
await for (final entity in _cacheDir.list()) {
if (entity is File) {
final stat = await entity.stat();
final age = now.difference(stat.modified);
if (age > _cacheExpiry) {
await entity.delete();
}
}
}
}
Future<void> _enforceCacheSizeLimit() async {
final currentSize = await getCacheSize();
if (currentSize <= _maxCacheSize) return;
final files = <File>[];
final fileStats = <File, FileStat>{};
await for (final entity in _cacheDir.list()) {
if (entity is File) {
files.add(entity);
fileStats[entity] = await entity.stat();
}
}
files.sort((a, b) {
final statA = fileStats[a]!;
final statB = fileStats[b]!;
return statA.modified.compareTo(statB.modified);
});
int sizeToRemove = currentSize - _maxCacheSize;
for (final file in files) {
if (sizeToRemove <= 0) break;
final fileSize = await file.length();
await file.delete();
sizeToRemove -= fileSize;
}
}
Future<void> dispose() async {
_initialized = false;
}
}