feat: instant tag suggestions, persistent disk cache, history limit settings, match highlighting and category badges (v2.0.9)
This commit is contained in:
@@ -16,6 +16,8 @@ class CacheStats {
|
||||
required this.imageMemoryCount,
|
||||
required this.tempDirectoryBytes,
|
||||
required this.downloadedCount,
|
||||
this.tagCacheCount = 0,
|
||||
this.tagCacheBytes = 0,
|
||||
});
|
||||
|
||||
final int cachedPostsCount;
|
||||
@@ -23,9 +25,14 @@ class CacheStats {
|
||||
final int imageMemoryCount;
|
||||
final int tempDirectoryBytes;
|
||||
final int downloadedCount;
|
||||
final int tagCacheCount;
|
||||
final int tagCacheBytes;
|
||||
|
||||
int get totalEstimatedBytes =>
|
||||
imageMemoryBytes + tempDirectoryBytes + (cachedPostsCount * 2048);
|
||||
imageMemoryBytes +
|
||||
tempDirectoryBytes +
|
||||
tagCacheBytes +
|
||||
(cachedPostsCount * 2048);
|
||||
|
||||
static const empty = CacheStats(
|
||||
cachedPostsCount: 0,
|
||||
@@ -33,6 +40,8 @@ class CacheStats {
|
||||
imageMemoryCount: 0,
|
||||
tempDirectoryBytes: 0,
|
||||
downloadedCount: 0,
|
||||
tagCacheCount: 0,
|
||||
tagCacheBytes: 0,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -64,14 +73,21 @@ final cacheStatsProvider = FutureProvider.autoDispose<CacheStats>((ref) async {
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
return CacheStats(
|
||||
cachedPostsCount: postsCount,
|
||||
imageMemoryBytes: memBytes,
|
||||
imageMemoryCount: memCount,
|
||||
tempDirectoryBytes: tempBytes,
|
||||
downloadedCount: downloadedCount,
|
||||
);
|
||||
});
|
||||
final tagCache = ref.watch(tagCacheServiceProvider);
|
||||
if (!tagCache.isInitialized) await tagCache.init();
|
||||
final tagCount = tagCache.tagCount;
|
||||
final tagBytes = await tagCache.getFileSizeBytes();
|
||||
|
||||
return CacheStats(
|
||||
cachedPostsCount: postsCount,
|
||||
imageMemoryBytes: memBytes,
|
||||
imageMemoryCount: memCount,
|
||||
tempDirectoryBytes: tempBytes,
|
||||
downloadedCount: downloadedCount,
|
||||
tagCacheCount: tagCount,
|
||||
tagCacheBytes: tagBytes,
|
||||
);
|
||||
});
|
||||
|
||||
class CacheManagerScreen extends ConsumerStatefulWidget {
|
||||
const CacheManagerScreen({super.key});
|
||||
@@ -127,9 +143,17 @@ class _CacheManagerScreenState extends ConsumerState<CacheManagerScreen> {
|
||||
if (mounted) setState(() => _isClearing = false);
|
||||
}
|
||||
|
||||
Future<void> _clearTagCache() async {
|
||||
setState(() => _isClearing = true);
|
||||
await ref.read(tagCacheServiceProvider).clear();
|
||||
ref.invalidate(cacheStatsProvider);
|
||||
if (mounted) setState(() => _isClearing = false);
|
||||
}
|
||||
|
||||
Future<void> _clearAllCache() async {
|
||||
setState(() => _isClearing = true);
|
||||
await ref.read(cacheServiceProvider).clear();
|
||||
await ref.read(tagCacheServiceProvider).clear();
|
||||
PaintingBinding.instance.imageCache.clear();
|
||||
PaintingBinding.instance.imageCache.clearLiveImages();
|
||||
try {
|
||||
@@ -244,6 +268,17 @@ class _CacheManagerScreenState extends ConsumerState<CacheManagerScreen> {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
_CacheCategoryTile(
|
||||
icon: Icons.label_rounded,
|
||||
title: strings.ru ? 'Кэш подсказок тегов' : 'Tag suggestions cache',
|
||||
subtitle: strings.ru
|
||||
? '${stats.tagCacheCount} закэшированных тегов на диске'
|
||||
: '${stats.tagCacheCount} cached tags on disk',
|
||||
sizeText: _formatBytes(stats.tagCacheBytes),
|
||||
onClear: _isClearing ? null : _clearTagCache,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
_CacheCategoryTile(
|
||||
icon: Icons.download_done_rounded,
|
||||
title: strings.ru ? 'Скачанные медиа' : 'Downloaded media',
|
||||
|
||||
@@ -38,6 +38,14 @@ class SettingsController extends AsyncNotifier<AppSettings> {
|
||||
ref.invalidateSelf();
|
||||
}
|
||||
|
||||
Future<void> clearSearchHistory() async {
|
||||
await ref.read(searchServiceProvider).clearHistory();
|
||||
}
|
||||
|
||||
Future<void> clearTagCache() async {
|
||||
await ref.read(searchServiceProvider).clearTagCache();
|
||||
}
|
||||
|
||||
Future<String> diagnosticsReport() async {
|
||||
final result =
|
||||
await ref.read(settingsServiceProvider).buildDiagnosticsReport(
|
||||
|
||||
@@ -392,6 +392,28 @@ class _SettingsContent extends ConsumerWidget {
|
||||
onChanged: (value) =>
|
||||
_update(ref, settings.copyWith(cacheMaxItems: value)),
|
||||
),
|
||||
_StepperTile(
|
||||
title: settings.languageCode == 'ru'
|
||||
? 'Лимит истории поиска'
|
||||
: 'Search history limit',
|
||||
value: settings.searchHistoryLimit,
|
||||
min: 50,
|
||||
max: 2000,
|
||||
step: 50,
|
||||
onChanged: (value) =>
|
||||
_update(ref, settings.copyWith(searchHistoryLimit: value)),
|
||||
),
|
||||
_StepperTile(
|
||||
title: settings.languageCode == 'ru'
|
||||
? 'Лимит кэша тегов (диск)'
|
||||
: 'Tag cache limit (disk)',
|
||||
value: settings.tagCacheLimit,
|
||||
min: 500,
|
||||
max: 20000,
|
||||
step: 500,
|
||||
onChanged: (value) =>
|
||||
_update(ref, settings.copyWith(tagCacheLimit: value)),
|
||||
),
|
||||
_ActionGrid(
|
||||
children: [
|
||||
FilledButton.tonalIcon(
|
||||
@@ -408,11 +430,59 @@ class _SettingsContent extends ConsumerWidget {
|
||||
? 'Менеджер кэша'
|
||||
: 'Cache Manager'),
|
||||
),
|
||||
FilledButton.tonalIcon(
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(settingsControllerProvider.notifier)
|
||||
.clearTagCache();
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
settings.languageCode == 'ru'
|
||||
? 'Кэш подсказок тегов очищен'
|
||||
: 'Tag suggestions cache cleared',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.label_off_rounded),
|
||||
label: Text(
|
||||
settings.languageCode == 'ru'
|
||||
? 'Очистить кэш тегов'
|
||||
: 'Clear tag cache',
|
||||
),
|
||||
),
|
||||
FilledButton.tonalIcon(
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(settingsControllerProvider.notifier)
|
||||
.clearSearchHistory();
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
settings.languageCode == 'ru'
|
||||
? 'История поиска очищена'
|
||||
: 'Search history cleared',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.history_toggle_off_rounded),
|
||||
label: Text(
|
||||
settings.languageCode == 'ru'
|
||||
? 'Очистить историю'
|
||||
: 'Clear search history',
|
||||
),
|
||||
),
|
||||
FilledButton.tonalIcon(
|
||||
onPressed: () => ref
|
||||
.read(settingsControllerProvider.notifier)
|
||||
.clearViewedHistory(),
|
||||
icon: const Icon(Icons.history_toggle_off_rounded),
|
||||
icon: const Icon(Icons.visibility_off_rounded),
|
||||
label: Text(strings.clearViewed),
|
||||
),
|
||||
FilledButton.tonalIcon(
|
||||
@@ -426,7 +496,7 @@ class _SettingsContent extends ConsumerWidget {
|
||||
onPressed: () => ref
|
||||
.read(settingsControllerProvider.notifier)
|
||||
.clearHiddenPosts(),
|
||||
icon: const Icon(Icons.visibility_off_rounded),
|
||||
icon: const Icon(Icons.delete_sweep_rounded),
|
||||
label:
|
||||
Text('Clear hidden (${settings.hiddenPostKeys.length})'),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user