import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import '../auth/auth_provider.dart' show secureStorageProvider; /// Operator-tunable cache settings. Persisted via /// flutter_secure_storage on the same device. /// /// #427 S2: the single `capBytes` is replaced by two independent /// budgets — Liked and Rolling-recent — each defaulting to 5GB. /// Bucketing is by liked-ness (a cached track currently in the /// user's liked set is charged to Liked; everything else to /// Rolling), so the dedup is storage-only and never filters /// playback. The old `cache_cap_bytes` key is intentionally not /// migrated; defaults reapply (a one-time re-tune, not data loss). class CacheSettings { const CacheSettings({ required this.likedCapBytes, required this.rollingCapBytes, required this.prefetchWindow, required this.cacheLikedTracks, }); /// Liked-bucket budget. 0 = unlimited. final int likedCapBytes; /// Rolling (recently-played) budget. 0 = unlimited. final int rollingCapBytes; /// 1..10. Number of next-tracks the prefetcher pre-downloads. final int prefetchWindow; /// When true, every like_track event triggers a pin with source autoLiked. final bool cacheLikedTracks; CacheSettings copyWith({ int? likedCapBytes, int? rollingCapBytes, int? prefetchWindow, bool? cacheLikedTracks, }) => CacheSettings( likedCapBytes: likedCapBytes ?? this.likedCapBytes, rollingCapBytes: rollingCapBytes ?? this.rollingCapBytes, prefetchWindow: prefetchWindow ?? this.prefetchWindow, cacheLikedTracks: cacheLikedTracks ?? this.cacheLikedTracks, ); static const _fiveGiB = 5 * 1024 * 1024 * 1024; static const defaults = CacheSettings( likedCapBytes: _fiveGiB, rollingCapBytes: _fiveGiB, prefetchWindow: 5, cacheLikedTracks: true, ); } class CacheSettingsController extends AsyncNotifier { static const _kLikedCap = 'cache_liked_cap_bytes'; static const _kRollingCap = 'cache_rolling_cap_bytes'; static const _kPrefetch = 'cache_prefetch_window'; static const _kCacheLiked = 'cache_liked_tracks'; late FlutterSecureStorage _storage; @override Future build() async { _storage = ref.read(secureStorageProvider); final likedCap = await _storage.read(key: _kLikedCap); final rollingCap = await _storage.read(key: _kRollingCap); final pre = await _storage.read(key: _kPrefetch); final liked = await _storage.read(key: _kCacheLiked); return CacheSettings( likedCapBytes: likedCap == null ? CacheSettings.defaults.likedCapBytes : int.tryParse(likedCap) ?? CacheSettings.defaults.likedCapBytes, rollingCapBytes: rollingCap == null ? CacheSettings.defaults.rollingCapBytes : int.tryParse(rollingCap) ?? CacheSettings.defaults.rollingCapBytes, prefetchWindow: pre == null ? CacheSettings.defaults.prefetchWindow : (int.tryParse(pre) ?? 5).clamp(1, 10), cacheLikedTracks: liked == null ? CacheSettings.defaults.cacheLikedTracks : liked == 'true', ); } Future setLikedCapBytes(int bytes) async { await _storage.write(key: _kLikedCap, value: bytes.toString()); state = AsyncData(state.value!.copyWith(likedCapBytes: bytes)); } Future setRollingCapBytes(int bytes) async { await _storage.write(key: _kRollingCap, value: bytes.toString()); state = AsyncData(state.value!.copyWith(rollingCapBytes: bytes)); } Future setPrefetchWindow(int n) async { final clamped = n.clamp(1, 10); await _storage.write(key: _kPrefetch, value: clamped.toString()); state = AsyncData(state.value!.copyWith(prefetchWindow: clamped)); } Future setCacheLikedTracks(bool on) async { await _storage.write(key: _kCacheLiked, value: on.toString()); state = AsyncData(state.value!.copyWith(cacheLikedTracks: on)); } } final cacheSettingsProvider = AsyncNotifierProvider( CacheSettingsController.new);