redesign app phase 3
Build / Build for Android (push) Has been cancelled
Build / Build for iOS (push) Has been cancelled

This commit is contained in:
Zakaria
2026-05-19 16:41:18 -04:00
parent c36fc55102
commit 9eb6b5237c
20 changed files with 645 additions and 275 deletions
+63 -20
View File
@@ -11,6 +11,7 @@ import 'package:path/path.dart' as path_helper;
import 'finamp_settings_helper.dart';
import 'finamp_user_helper.dart';
import 'download_support.dart';
import 'jellyfin_api.dart';
import 'jellyfin_api_helper.dart';
import 'get_internal_song_dir.dart';
@@ -61,6 +62,10 @@ class DownloadsHelper {
/// The view that this download is in. Used for sorting in offline mode.
required String viewId,
}) async {
if (!isDownloaderSupported) {
return Future.error("Downloads are only supported on Android and iOS");
}
// Check if we have external storage permission. It's a bit of a hack, but
// we only do this if downloadLocation.deletable is true because if it's
// true, we're downloading to a user location. You wouldn't want the app
@@ -228,6 +233,10 @@ class DownloadsHelper {
/// Gets the download status for the given item ids (Jellyfin item id, not flutter_downloader task id).
Future<List<DownloadTask>?> getDownloadStatus(List<String> itemIds) async {
try {
if (!isDownloaderSupported) {
return [];
}
List<String> downloadIds = [];
for (final itemId in itemIds) {
@@ -248,7 +257,8 @@ class DownloadsHelper {
}
}
Future<void> removeChildFromParent({required String parentId, required List<String> childIds}) async {
Future<void> removeChildFromParent(
{required String parentId, required List<String> childIds}) async {
var album = _downloadedParentsBox.get(parentId);
for (String childId in childIds) {
album?.downloadedChildren.removeWhere((key, value) => key == childId);
@@ -266,17 +276,18 @@ class DownloadsHelper {
_downloadsLogger.info(
"Could not find $jellyfinItemId in downloadedItemsBox, assuming already deleted");
} else {
DownloadedImage? downloadedImage = getDownloadedImage(downloadedSong.song);
DownloadedImage? downloadedImage =
getDownloadedImage(downloadedSong.song);
downloadedImage?.requiredBy.remove(jellyfinItemId);
if (downloadedSong.requiredBy.isEmpty) {
_downloadsLogger.info(
"Item $jellyfinItemId has no dependencies, deleting files");
_downloadsLogger
.info("Item $jellyfinItemId has no dependencies, deleting files");
_downloadsLogger.info(
"Deleting ${downloadedSong.downloadId} from flutter_downloader");
deleteDownloadFutures.add(FlutterDownloader.remove(
deleteDownloadFutures.add(_removeDownload(
taskId: downloadedSong.downloadId,
shouldDeleteContent: true,
file: downloadedSong.file,
));
await _downloadedItemsBox.delete(jellyfinItemId);
@@ -308,7 +319,8 @@ class DownloadsHelper {
/// This function will delete all given jellyfinItemIds regardless if they are still used
/// by other albums/playlists
Future<void> deleteDownloadChildren({required List<String> jellyfinItemIds, String? deletedFor}) async {
Future<void> deleteDownloadChildren(
{required List<String> jellyfinItemIds, String? deletedFor}) async {
final List<Future> deleteDownloadFutures = [];
final Map<String, Directory> directoriesToCheck = {};
@@ -321,11 +333,11 @@ class DownloadsHelper {
"Could not find $jellyfinItemId in downloadedItemsBox, assuming already deleted");
} else {
DownloadedImage? downloadedImage =
getDownloadedImage(downloadedSong.song);
getDownloadedImage(downloadedSong.song);
if (deletedFor != null) {
_downloadsLogger
.info("Removing $deletedFor dependency from $jellyfinItemId, current dependencies ${downloadedSong.requiredBy}");
_downloadsLogger.info(
"Removing $deletedFor dependency from $jellyfinItemId, current dependencies ${downloadedSong.requiredBy}");
downloadedSong.requiredBy.removeWhere((item) => item == deletedFor);
}
@@ -337,9 +349,9 @@ class DownloadsHelper {
_downloadsLogger.info(
"Deleting ${downloadedSong.downloadId} from flutter_downloader");
deleteDownloadFutures.add(FlutterDownloader.remove(
deleteDownloadFutures.add(_removeDownload(
taskId: downloadedSong.downloadId,
shouldDeleteContent: true,
file: downloadedSong.file,
));
await _downloadedItemsBox.delete(jellyfinItemId);
@@ -347,7 +359,7 @@ class DownloadsHelper {
if (deletedFor != null) {
DownloadedParent? downloadedAlbumTemp =
_downloadedParentsBox.get(deletedFor);
_downloadedParentsBox.get(deletedFor);
if (downloadedAlbumTemp != null) {
downloadedAlbumTemp.downloadedChildren.remove(jellyfinItemId);
await _downloadedParentsBox.put(deletedFor, downloadedAlbumTemp);
@@ -417,7 +429,8 @@ class DownloadsHelper {
String? deletedFor,
}) async {
try {
await deleteDownloadChildren(jellyfinItemIds: jellyfinItemIds, deletedFor: deletedFor);
await deleteDownloadChildren(
jellyfinItemIds: jellyfinItemIds, deletedFor: deletedFor);
if (deletedFor != null) {
await deleteDownloadParent(deletedFor: deletedFor);
}
@@ -445,12 +458,27 @@ class DownloadsHelper {
_downloadedImagesBox.delete(downloadedImage.id);
_downloadedImageIdsBox.delete(downloadedImage.downloadId);
await FlutterDownloader.remove(
await _removeDownload(
taskId: downloadedImage.downloadId,
shouldDeleteContent: true,
file: downloadedImage.file,
);
}
Future<void> _removeDownload(
{required String taskId, required File file}) async {
if (isDownloaderSupported) {
await FlutterDownloader.remove(
taskId: taskId,
shouldDeleteContent: true,
);
return;
}
if (await file.exists()) {
await file.delete();
}
}
/// Calculates the total file size of the given directory.
/// Returns the total file size in bytes.
/// Returns 0 if the directory doesn't exist.
@@ -480,6 +508,10 @@ class DownloadsHelper {
Future<List<DownloadTask>?> getIncompleteDownloads() async {
try {
if (!isDownloaderSupported) {
return [];
}
return await FlutterDownloader.loadTasksWithRawQuery(
query: "SELECT * FROM task WHERE status <> 3");
} catch (e) {
@@ -491,9 +523,13 @@ class DownloadsHelper {
Future<List<DownloadTask>?> getDownloadsWithStatus(
DownloadTaskStatus downloadTaskStatus) async {
try {
if (!isDownloaderSupported) {
return [];
}
return await FlutterDownloader.loadTasksWithRawQuery(
query:
"SELECT * FROM task WHERE status = ${downloadTaskStatus.value}");
"SELECT * FROM task WHERE status = ${downloadTaskStatus.index}");
} catch (e) {
_downloadsLogger.severe(e);
return Future.error(e);
@@ -851,6 +887,10 @@ class DownloadsHelper {
/// creating new ones with the same settings. Returns number of songs
/// redownloaded
Future<int> redownloadFailed() async {
if (!isDownloaderSupported) {
return 0;
}
final failedDownloadTasks =
await getDownloadsWithStatus(DownloadTaskStatus.failed);
@@ -883,8 +923,8 @@ class DownloadsHelper {
for (String parent in parents) {
// We don't specify deletedFor here because it could cause the parent
// to get deleted
deleteFutures
.add(deleteParentAndChildDownloads(jellyfinItemIds: [downloadedSong.song.id]));
deleteFutures.add(deleteParentAndChildDownloads(
jellyfinItemIds: [downloadedSong.song.id]));
if (parentItems[downloadedSong.song.id] == null) {
parentItems[downloadedSong.song.id] = [];
@@ -1055,7 +1095,6 @@ class DownloadsHelper {
{List<String>? keys}) =>
_downloadedItemsBox.listenable(keys: keys);
/// Converts a dart list to a string with the correct SQL syntax
String _dartListToSqlList(List dartList) {
try {
@@ -1106,6 +1145,10 @@ class DownloadsHelper {
}) async {
assert(item.blurHash != null);
if (!isDownloaderSupported) {
return Future.error("Downloads are only supported on Android and iOS");
}
if (_downloadedImagesBox.containsKey(item.blurHash)) return;
final imageUrl = _jellyfinApiData.getImageUrl(