cache_settings_screen.dart 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. import 'dart:io';
  2. import 'dart:math';
  3. import 'package:flutter/material.dart';
  4. import 'package:flutter_cache_manager/flutter_cache_manager.dart';
  5. import 'package:flutter_riverpod/flutter_riverpod.dart';
  6. import 'package:path_provider/path_provider.dart';
  7. import 'package:veloe_kemono_party_flutter/main.dart';
  8. class CacheSettingsScreen extends ConsumerStatefulWidget {
  9. const CacheSettingsScreen({super.key});
  10. @override
  11. ConsumerState<CacheSettingsScreen> createState() => _CacheSettingsScreenState();
  12. }
  13. class _CacheSettingsScreenState extends ConsumerState<CacheSettingsScreen> {
  14. final Map<String, CacheStats> _cacheStats = {
  15. 'Images': CacheStats('Images', 0, 0),
  16. 'Videos': CacheStats('Videos', 0, 0),
  17. 'Icons': CacheStats('Icons', 0, 0),
  18. };
  19. bool _isLoading = true;
  20. @override
  21. void initState() {
  22. super.initState();
  23. _loadCacheStats();
  24. }
  25. Future<void> _loadCacheStats() async {
  26. setState(() => _isLoading = true);
  27. final stats = await Future.wait([
  28. _getCacheStats(imageCacheManager, 'Images'),
  29. _getCacheStats(videoCacheManager, 'Videos'),
  30. _getCacheStats(iconsCacheManager, 'Icons'),
  31. ]);
  32. setState(() {
  33. for (var stat in stats) {
  34. _cacheStats[stat.name] = stat;
  35. }
  36. _isLoading = false;
  37. });
  38. }
  39. Future<CacheStats> _getCacheStats(CacheManager manager, String name) async {
  40. try {
  41. final directory = await getTemporaryDirectory();
  42. final cacheDir = Directory('${directory.path}/${manager.config.cacheKey}');
  43. if (!await cacheDir.exists()) return CacheStats(name, 0, 0);
  44. final files = await cacheDir.list().where((entity) => entity is File).toList();
  45. int totalSize = 0;
  46. for (var file in files.cast<File>()) {
  47. totalSize += await file.length();
  48. }
  49. return CacheStats(name, files.length, totalSize);
  50. } catch (e) {
  51. debugPrint('Cache stats error: $e');
  52. return CacheStats(name, 0, 0);
  53. }
  54. }
  55. Future<void> _clearCache(BaseCacheManager manager) async {
  56. await manager.emptyCache();
  57. _loadCacheStats();
  58. }
  59. String _formatBytes(int bytes) {
  60. if (bytes <= 0) return "0 B";
  61. const suffixes = ["B", "KB", "MB", "GB", "TB"];
  62. final i = (log(bytes) / log(1024)).floor();
  63. return '${(bytes / pow(1024, i)).toStringAsFixed(2)} ${suffixes[i]}';
  64. }
  65. @override
  66. Widget build(BuildContext context) {
  67. final currentSource = ref.watch(sourceProvider);
  68. return Scaffold(
  69. appBar: AppBar(
  70. title: const Text('Content Settings'),
  71. actions: [
  72. IconButton(
  73. icon: const Icon(Icons.refresh),
  74. onPressed: _loadCacheStats,
  75. tooltip: 'Refresh Stats',
  76. ),
  77. ],
  78. ),
  79. body: _isLoading
  80. ? const Center(child: CircularProgressIndicator())
  81. : Padding(
  82. padding: const EdgeInsets.all(16.0),
  83. child: Column(
  84. crossAxisAlignment: CrossAxisAlignment.start,
  85. children: [
  86. // Source selection
  87. Text('Content Source', style: Theme.of(context).textTheme.titleMedium),
  88. const SizedBox(height: 10),
  89. SegmentedButton<KemonoSource>(
  90. segments: const [
  91. ButtonSegment(
  92. value: KemonoSource.kemono,
  93. label: Text('Kemono.su'),
  94. icon: Icon(Icons.image),
  95. ),
  96. ButtonSegment(
  97. value: KemonoSource.coomer,
  98. label: Text('Coomer.su'),
  99. icon: Icon(Icons.person),
  100. ),
  101. ],
  102. selected: <KemonoSource>{currentSource},
  103. onSelectionChanged: (Set<KemonoSource> newSelection) {
  104. ref.read(sourceProvider.notifier).state = newSelection.first;
  105. ref.invalidate(kemonoClientProvider); // Invalidate client
  106. ref.invalidate(creatorsProvider); // Refresh creators list
  107. },
  108. ),
  109. const SizedBox(height: 30),
  110. // Cache statistics header
  111. Row(
  112. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  113. children: [
  114. Text('Cache Statistics', style: Theme.of(context).textTheme.titleMedium),
  115. TextButton(
  116. onPressed: () async {
  117. await imageCacheManager.emptyCache();
  118. await videoCacheManager.emptyCache();
  119. await iconsCacheManager.emptyCache();
  120. _loadCacheStats();
  121. },
  122. child: const Text('Clear All'),
  123. ),
  124. ],
  125. ),
  126. const SizedBox(height: 10),
  127. // Cache stats cards
  128. Expanded(
  129. child: ListView(
  130. children: [
  131. _buildCacheCard(_cacheStats['Images']!, imageCacheManager),
  132. _buildCacheCard(_cacheStats['Videos']!, videoCacheManager),
  133. _buildCacheCard(_cacheStats['Icons']!, iconsCacheManager),
  134. ],
  135. ),
  136. ),
  137. ],
  138. ),
  139. ),
  140. );
  141. }
  142. Widget _buildCacheCard(CacheStats stats, BaseCacheManager manager) {
  143. return Card(
  144. child: Padding(
  145. padding: const EdgeInsets.all(16.0),
  146. child: Column(
  147. crossAxisAlignment: CrossAxisAlignment.start,
  148. children: [
  149. Row(
  150. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  151. children: [
  152. Text(
  153. stats.name,
  154. style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
  155. ),
  156. IconButton(
  157. icon: const Icon(Icons.delete, size: 20),
  158. onPressed: () => _clearCache(manager),
  159. tooltip: 'Clear ${stats.name} Cache',
  160. ),
  161. ],
  162. ),
  163. const SizedBox(height: 10),
  164. Row(
  165. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  166. children: [
  167. const Text('Files:'),
  168. Text('${stats.fileCount}', style: const TextStyle(fontWeight: FontWeight.bold)),
  169. ],
  170. ),
  171. const SizedBox(height: 8),
  172. Row(
  173. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  174. children: [
  175. const Text('Size:'),
  176. Text(_formatBytes(stats.totalSize), style: const TextStyle(fontWeight: FontWeight.bold)),
  177. ],
  178. ),
  179. const SizedBox(height: 10),
  180. LinearProgressIndicator(
  181. value: stats.totalSize / (1024 * 1024 * 100), // 100MB scale
  182. minHeight: 6,
  183. backgroundColor: Colors.grey[200],
  184. valueColor: AlwaysStoppedAnimation<Color>(
  185. stats.name == 'Images' ? Colors.blue :
  186. stats.name == 'Videos' ? Colors.red : Colors.green,
  187. ),
  188. ),
  189. ],
  190. ),
  191. ),
  192. );
  193. }
  194. }
  195. class CacheStats {
  196. final String name;
  197. final int fileCount;
  198. final int totalSize;
  199. CacheStats(this.name, this.fileCount, this.totalSize);
  200. }