|
@@ -0,0 +1,223 @@
|
|
|
+import 'dart:io';
|
|
|
+import 'dart:math';
|
|
|
+import 'package:flutter/material.dart';
|
|
|
+import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
|
|
+import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
+import 'package:path_provider/path_provider.dart';
|
|
|
+import 'package:veloe_kemono_party_flutter/main.dart';
|
|
|
+
|
|
|
+
|
|
|
+class CacheSettingsScreen extends ConsumerStatefulWidget {
|
|
|
+ const CacheSettingsScreen({super.key});
|
|
|
+
|
|
|
+ @override
|
|
|
+ ConsumerState<CacheSettingsScreen> createState() => _CacheSettingsScreenState();
|
|
|
+}
|
|
|
+
|
|
|
+class _CacheSettingsScreenState extends ConsumerState<CacheSettingsScreen> {
|
|
|
+ final Map<String, CacheStats> _cacheStats = {
|
|
|
+ 'Images': CacheStats('Images', 0, 0),
|
|
|
+ 'Videos': CacheStats('Videos', 0, 0),
|
|
|
+ 'Icons': CacheStats('Icons', 0, 0),
|
|
|
+ };
|
|
|
+
|
|
|
+ bool _isLoading = true;
|
|
|
+
|
|
|
+ @override
|
|
|
+ void initState() {
|
|
|
+ super.initState();
|
|
|
+ _loadCacheStats();
|
|
|
+ }
|
|
|
+
|
|
|
+ Future<void> _loadCacheStats() async {
|
|
|
+ setState(() => _isLoading = true);
|
|
|
+
|
|
|
+ final stats = await Future.wait([
|
|
|
+ _getCacheStats(imageCacheManager, 'Images'),
|
|
|
+ _getCacheStats(videoCacheManager, 'Videos'),
|
|
|
+ _getCacheStats(iconsCacheManager, 'Icons'),
|
|
|
+ ]);
|
|
|
+
|
|
|
+ setState(() {
|
|
|
+ for (var stat in stats) {
|
|
|
+ _cacheStats[stat.name] = stat;
|
|
|
+ }
|
|
|
+ _isLoading = false;
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ Future<CacheStats> _getCacheStats(CacheManager manager, String name) async {
|
|
|
+ try {
|
|
|
+ final directory = await getTemporaryDirectory();
|
|
|
+ final cacheDir = Directory('${directory.path}/${manager.config.cacheKey}');
|
|
|
+
|
|
|
+ if (!await cacheDir.exists()) return CacheStats(name, 0, 0);
|
|
|
+
|
|
|
+ final files = await cacheDir.list().where((entity) => entity is File).toList();
|
|
|
+ int totalSize = 0;
|
|
|
+
|
|
|
+ for (var file in files.cast<File>()) {
|
|
|
+ totalSize += await file.length();
|
|
|
+ }
|
|
|
+
|
|
|
+ return CacheStats(name, files.length, totalSize);
|
|
|
+ } catch (e) {
|
|
|
+ debugPrint('Cache stats error: $e');
|
|
|
+ return CacheStats(name, 0, 0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Future<void> _clearCache(BaseCacheManager manager) async {
|
|
|
+ await manager.emptyCache();
|
|
|
+ _loadCacheStats();
|
|
|
+ }
|
|
|
+
|
|
|
+ String _formatBytes(int bytes) {
|
|
|
+ if (bytes <= 0) return "0 B";
|
|
|
+ const suffixes = ["B", "KB", "MB", "GB", "TB"];
|
|
|
+ final i = (log(bytes) / log(1024)).floor();
|
|
|
+ return '${(bytes / pow(1024, i)).toStringAsFixed(2)} ${suffixes[i]}';
|
|
|
+ }
|
|
|
+
|
|
|
+ @override
|
|
|
+ Widget build(BuildContext context) {
|
|
|
+ final currentSource = ref.watch(sourceProvider);
|
|
|
+
|
|
|
+ return Scaffold(
|
|
|
+ appBar: AppBar(
|
|
|
+ title: const Text('Content Settings'),
|
|
|
+ actions: [
|
|
|
+ IconButton(
|
|
|
+ icon: const Icon(Icons.refresh),
|
|
|
+ onPressed: _loadCacheStats,
|
|
|
+ tooltip: 'Refresh Stats',
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ body: _isLoading
|
|
|
+ ? const Center(child: CircularProgressIndicator())
|
|
|
+ : Padding(
|
|
|
+ padding: const EdgeInsets.all(16.0),
|
|
|
+ child: Column(
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
+ children: [
|
|
|
+ // Source selection
|
|
|
+ Text('Content Source', style: Theme.of(context).textTheme.titleMedium),
|
|
|
+ const SizedBox(height: 10),
|
|
|
+ SegmentedButton<KemonoSource>(
|
|
|
+ segments: const [
|
|
|
+ ButtonSegment(
|
|
|
+ value: KemonoSource.kemono,
|
|
|
+ label: Text('Kemono.su'),
|
|
|
+ icon: Icon(Icons.image),
|
|
|
+ ),
|
|
|
+ ButtonSegment(
|
|
|
+ value: KemonoSource.coomer,
|
|
|
+ label: Text('Coomer.su'),
|
|
|
+ icon: Icon(Icons.person),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ selected: <KemonoSource>{currentSource},
|
|
|
+ onSelectionChanged: (Set<KemonoSource> newSelection) {
|
|
|
+ ref.read(sourceProvider.notifier).state = newSelection.first;
|
|
|
+ ref.invalidate(kemonoClientProvider); // Invalidate client
|
|
|
+ ref.invalidate(creatorsProvider); // Refresh creators list
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ const SizedBox(height: 30),
|
|
|
+
|
|
|
+ // Cache statistics header
|
|
|
+ Row(
|
|
|
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
+ children: [
|
|
|
+ Text('Cache Statistics', style: Theme.of(context).textTheme.titleMedium),
|
|
|
+ TextButton(
|
|
|
+ onPressed: () async {
|
|
|
+ await imageCacheManager.emptyCache();
|
|
|
+ await videoCacheManager.emptyCache();
|
|
|
+ await iconsCacheManager.emptyCache();
|
|
|
+ _loadCacheStats();
|
|
|
+ },
|
|
|
+ child: const Text('Clear All'),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ const SizedBox(height: 10),
|
|
|
+
|
|
|
+ // Cache stats cards
|
|
|
+ Expanded(
|
|
|
+ child: ListView(
|
|
|
+ children: [
|
|
|
+ _buildCacheCard(_cacheStats['Images']!, imageCacheManager),
|
|
|
+ _buildCacheCard(_cacheStats['Videos']!, videoCacheManager),
|
|
|
+ _buildCacheCard(_cacheStats['Icons']!, iconsCacheManager),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ Widget _buildCacheCard(CacheStats stats, BaseCacheManager manager) {
|
|
|
+ return Card(
|
|
|
+ child: Padding(
|
|
|
+ padding: const EdgeInsets.all(16.0),
|
|
|
+ child: Column(
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
+ children: [
|
|
|
+ Row(
|
|
|
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
+ children: [
|
|
|
+ Text(
|
|
|
+ stats.name,
|
|
|
+ style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
|
|
+ ),
|
|
|
+ IconButton(
|
|
|
+ icon: const Icon(Icons.delete, size: 20),
|
|
|
+ onPressed: () => _clearCache(manager),
|
|
|
+ tooltip: 'Clear ${stats.name} Cache',
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ const SizedBox(height: 10),
|
|
|
+ Row(
|
|
|
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
+ children: [
|
|
|
+ const Text('Files:'),
|
|
|
+ Text('${stats.fileCount}', style: const TextStyle(fontWeight: FontWeight.bold)),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ const SizedBox(height: 8),
|
|
|
+ Row(
|
|
|
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
+ children: [
|
|
|
+ const Text('Size:'),
|
|
|
+ Text(_formatBytes(stats.totalSize), style: const TextStyle(fontWeight: FontWeight.bold)),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ const SizedBox(height: 10),
|
|
|
+ LinearProgressIndicator(
|
|
|
+ value: stats.totalSize / (1024 * 1024 * 100), // 100MB scale
|
|
|
+ minHeight: 6,
|
|
|
+ backgroundColor: Colors.grey[200],
|
|
|
+ valueColor: AlwaysStoppedAnimation<Color>(
|
|
|
+ stats.name == 'Images' ? Colors.blue :
|
|
|
+ stats.name == 'Videos' ? Colors.red : Colors.green,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+class CacheStats {
|
|
|
+ final String name;
|
|
|
+ final int fileCount;
|
|
|
+ final int totalSize;
|
|
|
+
|
|
|
+ CacheStats(this.name, this.fileCount, this.totalSize);
|
|
|
+}
|