feat(local folder): add cache export/clear actions and size of the folder

This commit is contained in:
Kingkor Roy Tirtho 2024-12-08 18:37:00 +06:00
parent 8eb99b0ef6
commit 94294a2a66
6 changed files with 483 additions and 33 deletions

View File

@ -39,3 +39,4 @@ analyzer:
- "**.g.dart"
- "**.gr.dart"
- "**/generated_plugin_registrant.dart"
- test/**/*.dart

View File

@ -125,4 +125,6 @@ abstract class SpotubeIcons {
static const folderAdd = FeatherIcons.folderPlus;
static const folderRemove = FeatherIcons.folderMinus;
static const cache = FeatherIcons.hardDrive;
static const export = Icons.file_open_outlined;
static const delete = FeatherIcons.trash2;
}

View File

@ -394,5 +394,12 @@
"unsupported_platform": "Unsupported platform",
"cache_music": "Cache music",
"open": "Open",
"cache_folder": "Cache folder"
"cache_folder": "Cache folder",
"export": "Export",
"clear_cache": "Clear cache",
"clear_cache_confirmation": "Do you want to clear the cache?",
"export_cache_files": "Export Cached Files",
"found_n_files": "Found {count} files",
"export_cache_confirmation": "Do you want to export these files to",
"exported_n_out_of_m_files": "Exported {filesExported} out of {files} files"
}

View File

@ -0,0 +1,139 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:path/path.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/services/logger/logger.dart';
import 'package:spotube/services/sourced_track/enums.dart';
final codecs = SourceCodecs.values.map((s) => s.name);
class LocalFolderCacheExportDialog extends HookConsumerWidget {
final Directory exportDir;
final Directory cacheDir;
const LocalFolderCacheExportDialog({
super.key,
required this.exportDir,
required this.cacheDir,
});
@override
Widget build(BuildContext context, ref) {
final ThemeData(:textTheme, :colorScheme) = Theme.of(context);
final files = useState<List<File>>([]);
final filesExported = useState<int>(0);
useEffect(() {
final stream = cacheDir.list().where(
(event) =>
event is File &&
codecs.contains(extension(event.path).replaceAll(".", "")),
);
stream.listen(
(event) {
files.value = [...files.value, event as File];
},
onError: (e, stack) {
AppLogger.reportError(e, stack);
},
);
return null;
}, []);
useEffect(() {
if (filesExported.value == files.value.length &&
filesExported.value > 0) {
Navigator.of(context).pop();
}
return null;
}, [filesExported.value, files.value]);
final isExportInProgress =
filesExported.value > 0 && filesExported.value != files.value.length;
return AlertDialog(
title: Text(context.l10n.export_cache_files),
content: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: filesExported.value == 0
? Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
context.l10n.found_n_files(files.value.length.toString()),
),
const Gap(10),
Text.rich(
TextSpan(
children: [
TextSpan(
text: context.l10n.export_cache_confirmation,
),
TextSpan(
text: "\n${exportDir.path}?",
style: textTheme.labelMedium!.copyWith(
color: colorScheme.secondary,
),
),
],
),
),
],
)
: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
context.l10n.exported_n_out_of_m_files(
files.value.length.toString(),
filesExported.value.toString(),
),
),
const Gap(10),
LinearProgressIndicator(
value: filesExported.value / files.value.length,
),
],
),
),
actions: [
TextButton(
onPressed: isExportInProgress
? null
: () {
Navigator.of(context).pop();
},
child: Text(context.l10n.cancel),
),
TextButton(
onPressed: isExportInProgress
? null
: () async {
for (final file in files.value) {
try {
final destinationFile = File(
join(exportDir.path, basename(file.path)),
);
if (await destinationFile.exists()) {
await destinationFile.delete();
}
await file.copy(destinationFile.path);
filesExported.value++;
} catch (e, stack) {
AppLogger.reportError(e, stack);
continue;
}
}
},
child: Text(context.l10n.export),
),
],
);
}
}

View File

@ -1,4 +1,8 @@
import 'dart:io';
import 'dart:math';
import 'package:collection/collection.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:fuzzywuzzy/fuzzywuzzy.dart';
@ -7,6 +11,7 @@ import 'package:skeletonizer/skeletonizer.dart';
import 'package:spotube/collections/fake.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/extensions/string.dart';
import 'package:spotube/modules/library/local_folder/cache_export_dialog.dart';
import 'package:spotube/modules/library/user_local_tracks.dart';
import 'package:spotube/components/expandable_search/expandable_search.dart';
import 'package:spotube/components/fallbacks/not_found.dart';
@ -19,6 +24,7 @@ import 'package:spotube/extensions/context.dart';
import 'package:spotube/models/local_track.dart';
import 'package:spotube/provider/local_tracks/local_tracks_provider.dart';
import 'package:spotube/provider/audio_player/audio_player.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
import 'package:spotube/utils/service_utils.dart';
class LocalLibraryPage extends HookConsumerWidget {
@ -59,6 +65,8 @@ class LocalLibraryPage extends HookConsumerWidget {
@override
Widget build(BuildContext context, ref) {
final ThemeData(:textTheme) = Theme.of(context);
final sortBy = useState<SortBy>(SortBy.none);
final playlist = ref.watch(audioPlayerProvider);
final trackSnapshot = ref.watch(localTracksProvider);
@ -72,20 +80,131 @@ class LocalLibraryPage extends HookConsumerWidget {
final controller = useScrollController();
final directorySize = useMemoized(() async {
final dir = Directory(location);
final files = await dir.list(recursive: true).toList();
final filesLength =
await Future.wait(files.whereType<File>().map((e) => e.length()));
return (filesLength.sum.toInt() / pow(10, 9)).toStringAsFixed(2);
}, [location]);
return SafeArea(
bottom: false,
child: Scaffold(
appBar: PageWindowTitleBar(
leading: const BackButton(),
centerTitle: true,
title: Text(
isDownloads
? context.l10n.downloads
: isCache
? context.l10n.cache_folder.capitalize()
: location,
title: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
isDownloads
? context.l10n.downloads
: isCache
? context.l10n.cache_folder.capitalize()
: location,
style: textTheme.titleLarge,
),
FutureBuilder<String>(
future: directorySize,
builder: (context, snapshot) {
return Text(
"${(snapshot.data ?? 0)} GB",
style: textTheme.labelSmall,
);
},
)
],
),
backgroundColor: Colors.transparent,
actions: [
if (isCache) ...[
IconButton(
icon: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(SpotubeIcons.delete),
Text(
context.l10n.clear_cache,
style: textTheme.labelSmall,
)
],
),
onPressed: () async {
final accepted = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog.adaptive(
title: Text(context.l10n.clear_cache_confirmation),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop(false);
},
child: Text(context.l10n.decline),
),
TextButton(
onPressed: () async {
Navigator.of(context).pop(true);
},
child: Text(context.l10n.accept),
),
],
),
);
if (accepted ?? false) return;
final cacheDir = Directory(
await UserPreferencesNotifier.getMusicCacheDir(),
);
if (cacheDir.existsSync()) {
await cacheDir.delete(recursive: true);
}
},
),
IconButton(
icon: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(SpotubeIcons.export),
Text(
context.l10n.export,
style: textTheme.labelSmall,
)
],
),
onPressed: () async {
final exportPath =
await FilePicker.platform.getDirectoryPath();
if (exportPath == null) return;
final exportDirectory = Directory(exportPath);
if (!exportDirectory.existsSync()) {
await exportDirectory.create(recursive: true);
}
final cacheDir = Directory(
await UserPreferencesNotifier.getMusicCacheDir());
if (!context.mounted) return;
await showDialog(
context: context,
builder: (context) {
return LocalFolderCacheExportDialog(
cacheDir: cacheDir,
exportDir: exportDirectory,
);
},
);
},
),
]
],
),
body: Column(
children: [

View File

@ -6,7 +6,14 @@
"invidious_source_description",
"cache_music",
"open",
"cache_folder"
"cache_folder",
"export",
"clear_cache",
"clear_cache_confirmation",
"export_cache_files",
"found_n_files",
"export_cache_confirmation",
"exported_n_out_of_m_files"
],
"bn": [
@ -16,7 +23,14 @@
"invidious_source_description",
"cache_music",
"open",
"cache_folder"
"cache_folder",
"export",
"clear_cache",
"clear_cache_confirmation",
"export_cache_files",
"found_n_files",
"export_cache_confirmation",
"exported_n_out_of_m_files"
],
"ca": [
@ -26,7 +40,14 @@
"invidious_source_description",
"cache_music",
"open",
"cache_folder"
"cache_folder",
"export",
"clear_cache",
"clear_cache_confirmation",
"export_cache_files",
"found_n_files",
"export_cache_confirmation",
"exported_n_out_of_m_files"
],
"cs": [
@ -36,7 +57,14 @@
"invidious_source_description",
"cache_music",
"open",
"cache_folder"
"cache_folder",
"export",
"clear_cache",
"clear_cache_confirmation",
"export_cache_files",
"found_n_files",
"export_cache_confirmation",
"exported_n_out_of_m_files"
],
"de": [
@ -46,7 +74,14 @@
"invidious_source_description",
"cache_music",
"open",
"cache_folder"
"cache_folder",
"export",
"clear_cache",
"clear_cache_confirmation",
"export_cache_files",
"found_n_files",
"export_cache_confirmation",
"exported_n_out_of_m_files"
],
"es": [
@ -56,7 +91,14 @@
"invidious_source_description",
"cache_music",
"open",
"cache_folder"
"cache_folder",
"export",
"clear_cache",
"clear_cache_confirmation",
"export_cache_files",
"found_n_files",
"export_cache_confirmation",
"exported_n_out_of_m_files"
],
"eu": [
@ -66,7 +108,14 @@
"invidious_source_description",
"cache_music",
"open",
"cache_folder"
"cache_folder",
"export",
"clear_cache",
"clear_cache_confirmation",
"export_cache_files",
"found_n_files",
"export_cache_confirmation",
"exported_n_out_of_m_files"
],
"fa": [
@ -76,7 +125,14 @@
"invidious_source_description",
"cache_music",
"open",
"cache_folder"
"cache_folder",
"export",
"clear_cache",
"clear_cache_confirmation",
"export_cache_files",
"found_n_files",
"export_cache_confirmation",
"exported_n_out_of_m_files"
],
"fi": [
@ -86,7 +142,14 @@
"invidious_source_description",
"cache_music",
"open",
"cache_folder"
"cache_folder",
"export",
"clear_cache",
"clear_cache_confirmation",
"export_cache_files",
"found_n_files",
"export_cache_confirmation",
"exported_n_out_of_m_files"
],
"fr": [
@ -96,7 +159,14 @@
"invidious_source_description",
"cache_music",
"open",
"cache_folder"
"cache_folder",
"export",
"clear_cache",
"clear_cache_confirmation",
"export_cache_files",
"found_n_files",
"export_cache_confirmation",
"exported_n_out_of_m_files"
],
"hi": [
@ -106,7 +176,14 @@
"invidious_source_description",
"cache_music",
"open",
"cache_folder"
"cache_folder",
"export",
"clear_cache",
"clear_cache_confirmation",
"export_cache_files",
"found_n_files",
"export_cache_confirmation",
"exported_n_out_of_m_files"
],
"id": [
@ -116,7 +193,14 @@
"invidious_source_description",
"cache_music",
"open",
"cache_folder"
"cache_folder",
"export",
"clear_cache",
"clear_cache_confirmation",
"export_cache_files",
"found_n_files",
"export_cache_confirmation",
"exported_n_out_of_m_files"
],
"it": [
@ -126,7 +210,14 @@
"invidious_source_description",
"cache_music",
"open",
"cache_folder"
"cache_folder",
"export",
"clear_cache",
"clear_cache_confirmation",
"export_cache_files",
"found_n_files",
"export_cache_confirmation",
"exported_n_out_of_m_files"
],
"ja": [
@ -136,7 +227,14 @@
"invidious_source_description",
"cache_music",
"open",
"cache_folder"
"cache_folder",
"export",
"clear_cache",
"clear_cache_confirmation",
"export_cache_files",
"found_n_files",
"export_cache_confirmation",
"exported_n_out_of_m_files"
],
"ka": [
@ -146,7 +244,14 @@
"invidious_source_description",
"cache_music",
"open",
"cache_folder"
"cache_folder",
"export",
"clear_cache",
"clear_cache_confirmation",
"export_cache_files",
"found_n_files",
"export_cache_confirmation",
"exported_n_out_of_m_files"
],
"ko": [
@ -156,7 +261,14 @@
"invidious_source_description",
"cache_music",
"open",
"cache_folder"
"cache_folder",
"export",
"clear_cache",
"clear_cache_confirmation",
"export_cache_files",
"found_n_files",
"export_cache_confirmation",
"exported_n_out_of_m_files"
],
"ne": [
@ -166,7 +278,14 @@
"invidious_source_description",
"cache_music",
"open",
"cache_folder"
"cache_folder",
"export",
"clear_cache",
"clear_cache_confirmation",
"export_cache_files",
"found_n_files",
"export_cache_confirmation",
"exported_n_out_of_m_files"
],
"nl": [
@ -176,7 +295,14 @@
"invidious_source_description",
"cache_music",
"open",
"cache_folder"
"cache_folder",
"export",
"clear_cache",
"clear_cache_confirmation",
"export_cache_files",
"found_n_files",
"export_cache_confirmation",
"exported_n_out_of_m_files"
],
"pl": [
@ -186,7 +312,14 @@
"invidious_source_description",
"cache_music",
"open",
"cache_folder"
"cache_folder",
"export",
"clear_cache",
"clear_cache_confirmation",
"export_cache_files",
"found_n_files",
"export_cache_confirmation",
"exported_n_out_of_m_files"
],
"pt": [
@ -196,7 +329,14 @@
"invidious_source_description",
"cache_music",
"open",
"cache_folder"
"cache_folder",
"export",
"clear_cache",
"clear_cache_confirmation",
"export_cache_files",
"found_n_files",
"export_cache_confirmation",
"exported_n_out_of_m_files"
],
"ru": [
@ -206,7 +346,14 @@
"invidious_source_description",
"cache_music",
"open",
"cache_folder"
"cache_folder",
"export",
"clear_cache",
"clear_cache_confirmation",
"export_cache_files",
"found_n_files",
"export_cache_confirmation",
"exported_n_out_of_m_files"
],
"th": [
@ -216,7 +363,14 @@
"invidious_source_description",
"cache_music",
"open",
"cache_folder"
"cache_folder",
"export",
"clear_cache",
"clear_cache_confirmation",
"export_cache_files",
"found_n_files",
"export_cache_confirmation",
"exported_n_out_of_m_files"
],
"tr": [
@ -226,7 +380,14 @@
"invidious_source_description",
"cache_music",
"open",
"cache_folder"
"cache_folder",
"export",
"clear_cache",
"clear_cache_confirmation",
"export_cache_files",
"found_n_files",
"export_cache_confirmation",
"exported_n_out_of_m_files"
],
"uk": [
@ -236,7 +397,14 @@
"invidious_source_description",
"cache_music",
"open",
"cache_folder"
"cache_folder",
"export",
"clear_cache",
"clear_cache_confirmation",
"export_cache_files",
"found_n_files",
"export_cache_confirmation",
"exported_n_out_of_m_files"
],
"vi": [
@ -246,7 +414,14 @@
"invidious_source_description",
"cache_music",
"open",
"cache_folder"
"cache_folder",
"export",
"clear_cache",
"clear_cache_confirmation",
"export_cache_files",
"found_n_files",
"export_cache_confirmation",
"exported_n_out_of_m_files"
],
"zh": [
@ -256,6 +431,13 @@
"invidious_source_description",
"cache_music",
"open",
"cache_folder"
"cache_folder",
"export",
"clear_cache",
"clear_cache_confirmation",
"export_cache_files",
"found_n_files",
"export_cache_confirmation",
"exported_n_out_of_m_files"
]
}