Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to clear image cache on startup, and manually #1062

Merged
merged 2 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@
},
"changeSort": "Change Sort",
"@changeSort": {},
"clearCache": "Clear Cache ({cacheSize})",
"@clearCache": {
"description": "Label for action to clear cache in the dialog"
},
"clearDatabase": "Clear Database",
"@clearDatabase": {
"description": "Label for action to clear local database in the dialog"
Expand All @@ -119,6 +123,10 @@
},
"clearSearch": "Clear Search",
"@clearSearch": {},
"clearedCache": "Cleared cache successfully.",
"@clearedCache": {
"description": "Message indicating that cache was cleared successfully"
},
"clearedDatabase": "Local database cleared. Restart Thunder for new changes to take effect.",
"@clearedDatabase": {},
"clearedUserPreferences": "Cleared all user preferences",
Expand Down
4 changes: 4 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import 'package:thunder/core/theme/bloc/theme_bloc.dart';
import 'package:thunder/core/auth/bloc/auth_bloc.dart';
import 'package:thunder/thunder/thunder.dart';
import 'package:thunder/user/bloc/user_bloc.dart';
import 'package:thunder/utils/cache.dart';
import 'package:thunder/utils/global_context.dart';
import 'package:flutter/foundation.dart';

Expand All @@ -42,6 +43,9 @@ void main() async {
// Load up sqlite database
await DB.instance.database;

// Clear image cache
await clearExtendedImageCache();

// Register dart_ping on iOS
if (!kIsWeb && Platform.isIOS) {
DartPingIOS.register();
Expand Down
46 changes: 44 additions & 2 deletions lib/settings/pages/debug_settings_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,25 @@ import 'package:flutter/material.dart';

import 'package:path/path.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:extended_image/extended_image.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:thunder/shared/dialogs.dart';

import 'package:thunder/shared/dialogs.dart';
import 'package:thunder/shared/snackbar.dart';
import 'package:thunder/thunder/bloc/thunder_bloc.dart';
import 'package:thunder/settings/widgets/settings_list_tile.dart';
import 'package:thunder/utils/cache.dart';

class DebugSettingsPage extends StatelessWidget {
class DebugSettingsPage extends StatefulWidget {
const DebugSettingsPage({super.key});

@override
State<DebugSettingsPage> createState() => _DebugSettingsPageState();
}

class _DebugSettingsPageState extends State<DebugSettingsPage> {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
Expand Down Expand Up @@ -114,6 +121,41 @@ class DebugSettingsPage extends StatelessWidget {
),
),
),
SliverToBoxAdapter(
child: Divider(
indent: 32.0,
height: 32.0,
endIndent: 32.0,
thickness: 2.0,
color: theme.dividerColor.withOpacity(0.6),
),
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: FutureBuilder<int>(
future: getExtendedImageCacheSize(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return SettingsListTile(
icon: Icons.data_saver_off_rounded,
description: l10n.clearCache('${(snapshot.data! / (1024 * 1024)).toStringAsFixed(2)} MB'),
widget: const SizedBox(
height: 42.0,
child: Icon(Icons.chevron_right_rounded),
),
onTap: () async {
await clearDiskCachedImages();
if (context.mounted) showSnackbar(context, l10n.clearedCache);
setState(() {}); // Trigger a rebuild to refresh the cache size
},
);
}
return Container();
},
),
),
),
],
),
);
Expand Down
39 changes: 39 additions & 0 deletions lib/utils/cache.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
import 'dart:io';

import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:extended_image/extended_image.dart';

/// Defines a cache class which can be used to store values in memory for a certain [expiration]
/// or else re-fetch the value using the given [getValue] function.
class Cache<T> {
Expand All @@ -12,3 +18,36 @@ class Cache<T> {
T? _value;
DateTime? _lastSetTime;
}

/// Returns the total size of the image cache from ExtendedImage
Future<int> getExtendedImageCacheSize() async {
try {
final Directory cacheImagesDirectory = Directory(join((await getTemporaryDirectory()).path, cacheImageFolderName));
if (!cacheImagesDirectory.existsSync()) return 0;

int totalSize = 0;

// Iterate over the files in the directory
await for (final FileSystemEntity file in cacheImagesDirectory.list()) {
try {
final FileStat fs = file.statSync();
totalSize += fs.size;
} catch (e) {
// Ignore errors
}
}

return totalSize;
} catch (e) {
return -1; // Return -1 if an error occurs
}
}

/// Clears the image cache from ExtendedImage, by deleting all files older than [duration].
/// If [duration] is not provided, it defaults to 7 days.
Future<void> clearExtendedImageCache({Duration expiration = const Duration(days: 7)}) async {
final Directory cacheImagesDirectory = Directory(join((await getTemporaryDirectory()).path, cacheImageFolderName));
if (!cacheImagesDirectory.existsSync()) return;

await clearDiskCachedImages(duration: expiration);
}
Loading