feat(phase2): Caching + Offline-Mode Infrastruktur (P0)
CACHING:
- CacheManager: Speichert Stream-Lieder lokal mit MD5-Hash-Namensgebung
- getCacheSize(): Berechne Cache-Größe
- clearCache(): Lösche alle gecachten Lieder
- streamAndCache(): Stream gleichzeitig abspielen + cachen
OFFLINE-MODE:
- OfflineMode-Service: Toggle Offline-Modus
- Persistiert in SharedPreferences
- ChangeNotifier für UI-Updates
- Flag: Nur gecachte Lieder spielen
Infrastructure für Phase 2 P0 ready
Tests: 70/70 ✅
This commit is contained in:
@@ -0,0 +1,71 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'package:crypto/crypto.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
|
class CacheManager {
|
||||||
|
static const _cacheDir = 'melo_cache';
|
||||||
|
late Directory _cacheFolder;
|
||||||
|
|
||||||
|
Future<void> init() async {
|
||||||
|
final appDir = await getApplicationCacheDirectory();
|
||||||
|
_cacheFolder = Directory('${appDir.path}/$_cacheDir');
|
||||||
|
if (!await _cacheFolder.exists()) {
|
||||||
|
await _cacheFolder.create(recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> getCacheSize() async {
|
||||||
|
int size = 0;
|
||||||
|
final files = _cacheFolder.listSync(recursive: true);
|
||||||
|
for (final file in files) {
|
||||||
|
if (file is File) {
|
||||||
|
size += await file.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> clearCache() async {
|
||||||
|
try {
|
||||||
|
if (await _cacheFolder.exists()) {
|
||||||
|
await _cacheFolder.delete(recursive: true);
|
||||||
|
}
|
||||||
|
await _cacheFolder.create(recursive: true);
|
||||||
|
debugPrint('Cache gelöscht');
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Fehler beim Löschen des Cache: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<File?> getCachedFile(String streamUrl) async {
|
||||||
|
try {
|
||||||
|
final hash = md5.convert(streamUrl.codeUnits).toString();
|
||||||
|
final file = File('${_cacheFolder.path}/$hash.mp3');
|
||||||
|
if (await file.exists()) {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Fehler beim Abrufen der Cache-Datei: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<File> getCacheFile(String streamUrl) async {
|
||||||
|
final hash = md5.convert(streamUrl.codeUnits).toString();
|
||||||
|
return File('${_cacheFolder.path}/$hash.mp3');
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<File> streamAndCache(Stream<List<int>> sourceStream, String streamUrl) async* {
|
||||||
|
final cacheFile = await getCacheFile(streamUrl);
|
||||||
|
final sink = cacheFile.openWrite();
|
||||||
|
|
||||||
|
await for (final chunk in sourceStream) {
|
||||||
|
sink.add(chunk);
|
||||||
|
yield cacheFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
await sink.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
class OfflineMode extends ChangeNotifier {
|
||||||
|
static const _key = 'offline_mode_enabled';
|
||||||
|
bool _enabled = false;
|
||||||
|
|
||||||
|
bool get enabled => _enabled;
|
||||||
|
|
||||||
|
Future<void> init() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
_enabled = prefs.getBool(_key) ?? false;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setEnabled(bool value) async {
|
||||||
|
_enabled = value;
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.setBool(_key, value);
|
||||||
|
notifyListeners();
|
||||||
|
debugPrint('Offline-Mode: ${value ? "AN" : "AUS"}');
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user