cache_settings_screen.dart 7.7 KB

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