From 1a660da273f16fc27c5efa1ac636df7412f72051 Mon Sep 17 00:00:00 2001 From: rom4nik <46846000+rom4nik@users.noreply.github.com> Date: Mon, 7 Mar 2022 22:34:35 +0100 Subject: [PATCH 01/13] Add confirmation dialog before deleting downloaded album --- .../AlbumScreen/DownloadButton.dart | 23 ++++------ .../DownloadedAlbumDeleteDialog.dart | 43 +++++++++++++++++++ 2 files changed, 52 insertions(+), 14 deletions(-) create mode 100644 lib/components/AlbumScreen/DownloadedAlbumDeleteDialog.dart diff --git a/lib/components/AlbumScreen/DownloadButton.dart b/lib/components/AlbumScreen/DownloadButton.dart index 1cf76bbb1..fc8bc85a2 100644 --- a/lib/components/AlbumScreen/DownloadButton.dart +++ b/lib/components/AlbumScreen/DownloadButton.dart @@ -7,8 +7,8 @@ import '../../services/FinampSettingsHelper.dart'; import '../../services/JellyfinApiData.dart'; import '../../models/JellyfinModels.dart'; import '../../models/FinampModels.dart'; -import '../errorSnackbar.dart'; import 'DownloadDialog.dart'; +import 'DownloadedAlbumDeleteDialog.dart'; class DownloadButton extends StatefulWidget { const DownloadButton({ @@ -53,23 +53,18 @@ class _DownloadButtonState extends State { // If offline, we don't allow the user to delete items. // If we did, we'd have to implement listeners for MusicScreenTabView so that the user can't delete a parent, go back, and select the same parent. // If they did, AlbumScreen would show an error since the item no longer exists. - // Also, the user could delete the parent and immediately redownload it, which will either cause unwanted network usage or cause more errors becuase the user is offline. + // Also, the user could delete the parent and immediately redownload it, which will either cause unwanted network usage or cause more errors because the user is offline. onPressed: isOffline ?? false ? null : () { if (isDownloaded) { - _downloadsHelper - .deleteDownloads( - jellyfinItemIds: widget.items.map((e) => e.id).toList(), - deletedFor: widget.parent.id, - ) - .then((_) { - _checkIfDownloaded(); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text("Downloads deleted."))); - }, - onError: (error, stackTrace) => - errorSnackbar(error, context)); + showDialog( + context: context, + builder: (context) => DownloadedAlbumDeleteDialog( + items: widget.items, + parent: widget.parent, + ), + ).whenComplete(() => _checkIfDownloaded()); } else { if (FinampSettingsHelper .finampSettings.downloadLocationsMap.length == diff --git a/lib/components/AlbumScreen/DownloadedAlbumDeleteDialog.dart b/lib/components/AlbumScreen/DownloadedAlbumDeleteDialog.dart new file mode 100644 index 000000000..9519b92cc --- /dev/null +++ b/lib/components/AlbumScreen/DownloadedAlbumDeleteDialog.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; + +import '../../models/JellyfinModels.dart'; +import '../../services/DownloadsHelper.dart'; +import '../errorSnackbar.dart'; + +class DownloadedAlbumDeleteDialog extends StatelessWidget { + const DownloadedAlbumDeleteDialog({ + Key? key, + required this.items, + required this.parent, + }) : super(key: key); + + final List items; + final BaseItemDto parent; + + @override + Widget build(BuildContext context) { + final _downloadsHelper = GetIt.instance(); + return AlertDialog( + title: const Text("Are you sure?"), + actions: [ + TextButton( + child: const Text("CANCEL"), + onPressed: () => Navigator.of(context).pop(), + ), + TextButton( + child: const Text("DELETE"), + onPressed: () { + _downloadsHelper.deleteDownloads( + jellyfinItemIds: items.map((e) => e.id).toList(), + deletedFor: parent.id + ).onError((error, stackTrace) => errorSnackbar(error, context)); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text("Downloads deleted."))); + Navigator.of(context).pop(); + }, + ), + ], + ); + } +} From 55f05e93948b08c00f2a8af3c4ada5009c9dcf1e Mon Sep 17 00:00:00 2001 From: rom4nik <46846000+rom4nik@users.noreply.github.com> Date: Wed, 18 May 2022 18:21:27 +0200 Subject: [PATCH 02/13] Don't exit from DownloadedAlbumDeleteDialog until downloads get deleted --- .../AlbumScreen/DownloadedAlbumDeleteDialog.dart | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/components/AlbumScreen/DownloadedAlbumDeleteDialog.dart b/lib/components/AlbumScreen/DownloadedAlbumDeleteDialog.dart index 9519b92cc..40b6b9472 100644 --- a/lib/components/AlbumScreen/DownloadedAlbumDeleteDialog.dart +++ b/lib/components/AlbumScreen/DownloadedAlbumDeleteDialog.dart @@ -15,6 +15,14 @@ class DownloadedAlbumDeleteDialog extends StatelessWidget { final List items; final BaseItemDto parent; + // used to make sure isDownloaded in DownloadButton is checked after downloads + // actually get deleted + void exitDialog(BuildContext context) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text("Downloads deleted."))); + Navigator.of(context).pop(); + } + @override Widget build(BuildContext context) { final _downloadsHelper = GetIt.instance(); @@ -31,10 +39,8 @@ class DownloadedAlbumDeleteDialog extends StatelessWidget { _downloadsHelper.deleteDownloads( jellyfinItemIds: items.map((e) => e.id).toList(), deletedFor: parent.id - ).onError((error, stackTrace) => errorSnackbar(error, context)); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text("Downloads deleted."))); - Navigator.of(context).pop(); + ).whenComplete(() => exitDialog(context)) + .onError((error, stackTrace) => errorSnackbar(error, context)); }, ), ], From d984d43a41ddae33de4ae22f82a66126b90bb828 Mon Sep 17 00:00:00 2001 From: Chaphasilor Date: Thu, 12 Oct 2023 19:25:25 +0200 Subject: [PATCH 03/13] use callbacks to handle dialog results --- .../downloaded_album_delete_dialog.dart | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 lib/components/AlbumScreen/downloaded_album_delete_dialog.dart diff --git a/lib/components/AlbumScreen/downloaded_album_delete_dialog.dart b/lib/components/AlbumScreen/downloaded_album_delete_dialog.dart new file mode 100644 index 000000000..2630cdd85 --- /dev/null +++ b/lib/components/AlbumScreen/downloaded_album_delete_dialog.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; + +import '../../models/jellyfin_models.dart'; +import '../../services/downloads_helper.dart'; +import '../error_snackbar.dart'; + +class DownloadedAlbumDeleteDialog extends AlertDialog { + const DownloadedAlbumDeleteDialog({ + Key? key, + required this.onConfirmed, + required this.onAborted, + }) : super(key: key); + + final void Function()? onConfirmed; + final void Function()? onAborted; + + // used to make sure isDownloaded in DownloadButton is checked after downloads + // actually get deleted + void exitDialog(BuildContext context) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text("Downloads deleted."))); + Navigator.of(context).pop(); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text("Are you sure?"), + actions: [ + TextButton( + child: const Text("CANCEL"), + onPressed: () { + Navigator.of(context).pop(); + onAborted?.call(); + }, + ), + TextButton( + child: const Text("DELETE"), + onPressed: () { + exitDialog(context); + onConfirmed?.call(); + }, + ), + ], + ); + } +} From b9bc90161906d1ade26462e57da8349d56989ee6 Mon Sep 17 00:00:00 2001 From: Chaphasilor Date: Tue, 28 Nov 2023 23:21:52 +0100 Subject: [PATCH 04/13] refactor dialog - made fully generic now --- .../AlbumScreen/download_button.dart | 21 +++++++++++++------ ...g.dart => confirmation_prompt_dialog.dart} | 18 +++++----------- 2 files changed, 20 insertions(+), 19 deletions(-) rename lib/components/{AlbumScreen/downloaded_album_delete_dialog.dart => confirmation_prompt_dialog.dart} (57%) diff --git a/lib/components/AlbumScreen/download_button.dart b/lib/components/AlbumScreen/download_button.dart index dcee55f4b..ec6d692ac 100644 --- a/lib/components/AlbumScreen/download_button.dart +++ b/lib/components/AlbumScreen/download_button.dart @@ -10,7 +10,7 @@ import '../../models/jellyfin_models.dart'; import '../../models/finamp_models.dart'; import '../error_snackbar.dart'; import 'download_dialog.dart'; -import 'downloaded_album_delete_dialog.dart'; +import '../confirmation_prompt_dialog.dart'; class DownloadButton extends StatefulWidget { const DownloadButton({ @@ -65,12 +65,21 @@ class _DownloadButtonState extends State { if (isDownloaded) { showDialog( context: context, - builder: (context) => DownloadedAlbumDeleteDialog( + builder: (context) => ConfirmationPromptDialog( onConfirmed: () { - _downloadsHelper.deleteDownloads( - jellyfinItemIds: widget.items.map((e) => e.id).toList(), - deletedFor: widget.parent.id - ).onError((error, stackTrace) => errorSnackbar(error, context)); + Navigator.of(context).pop(); + _downloadsHelper + .deleteDownloads( + jellyfinItemIds: + widget.items.map((e) => e.id).toList(), + deletedFor: widget.parent.id) + .then((value) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text("Downloads deleted."))); + }).onError((error, stackTrace) { + errorSnackbar(error, context); + }); }, onAborted: () {}, ), diff --git a/lib/components/AlbumScreen/downloaded_album_delete_dialog.dart b/lib/components/confirmation_prompt_dialog.dart similarity index 57% rename from lib/components/AlbumScreen/downloaded_album_delete_dialog.dart rename to lib/components/confirmation_prompt_dialog.dart index 2630cdd85..53bf1d4c8 100644 --- a/lib/components/AlbumScreen/downloaded_album_delete_dialog.dart +++ b/lib/components/confirmation_prompt_dialog.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; -import '../../models/jellyfin_models.dart'; -import '../../services/downloads_helper.dart'; -import '../error_snackbar.dart'; +import '../models/jellyfin_models.dart'; +import '../services/downloads_helper.dart'; +import 'error_snackbar.dart'; -class DownloadedAlbumDeleteDialog extends AlertDialog { - const DownloadedAlbumDeleteDialog({ +class ConfirmationPromptDialog extends AlertDialog { + const ConfirmationPromptDialog({ Key? key, required this.onConfirmed, required this.onAborted, @@ -15,13 +15,6 @@ class DownloadedAlbumDeleteDialog extends AlertDialog { final void Function()? onConfirmed; final void Function()? onAborted; - // used to make sure isDownloaded in DownloadButton is checked after downloads - // actually get deleted - void exitDialog(BuildContext context) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text("Downloads deleted."))); - Navigator.of(context).pop(); - } @override Widget build(BuildContext context) { @@ -38,7 +31,6 @@ class DownloadedAlbumDeleteDialog extends AlertDialog { TextButton( child: const Text("DELETE"), onPressed: () { - exitDialog(context); onConfirmed?.call(); }, ), From 7c501fff1d10286a7bcefb52cf1a8e08e49a1cbc Mon Sep 17 00:00:00 2001 From: Chaphasilor Date: Tue, 28 Nov 2023 23:27:10 +0100 Subject: [PATCH 05/13] close dialog before callback --- lib/components/AlbumScreen/download_button.dart | 1 - lib/components/confirmation_prompt_dialog.dart | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/AlbumScreen/download_button.dart b/lib/components/AlbumScreen/download_button.dart index ec6d692ac..aac68b0b3 100644 --- a/lib/components/AlbumScreen/download_button.dart +++ b/lib/components/AlbumScreen/download_button.dart @@ -67,7 +67,6 @@ class _DownloadButtonState extends State { context: context, builder: (context) => ConfirmationPromptDialog( onConfirmed: () { - Navigator.of(context).pop(); _downloadsHelper .deleteDownloads( jellyfinItemIds: diff --git a/lib/components/confirmation_prompt_dialog.dart b/lib/components/confirmation_prompt_dialog.dart index 53bf1d4c8..a13bdbe6a 100644 --- a/lib/components/confirmation_prompt_dialog.dart +++ b/lib/components/confirmation_prompt_dialog.dart @@ -31,6 +31,7 @@ class ConfirmationPromptDialog extends AlertDialog { TextButton( child: const Text("DELETE"), onPressed: () { + Navigator.of(context).pop(); // Close the dialog onConfirmed?.call(); }, ), From 75fa6fa17360157aa9678238a228199715576f5c Mon Sep 17 00:00:00 2001 From: Chaphasilor Date: Wed, 29 Nov 2023 23:50:21 +0100 Subject: [PATCH 06/13] move dialog text config into constructor --- lib/components/AlbumScreen/download_button.dart | 3 +++ lib/components/confirmation_prompt_dialog.dart | 12 +++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/components/AlbumScreen/download_button.dart b/lib/components/AlbumScreen/download_button.dart index aac68b0b3..9ead51584 100644 --- a/lib/components/AlbumScreen/download_button.dart +++ b/lib/components/AlbumScreen/download_button.dart @@ -66,6 +66,9 @@ class _DownloadButtonState extends State { showDialog( context: context, builder: (context) => ConfirmationPromptDialog( + promptText: "Are you sure?", + confirmButtonText: "Delete", + abortButtonText: "Cancel", onConfirmed: () { _downloadsHelper .deleteDownloads( diff --git a/lib/components/confirmation_prompt_dialog.dart b/lib/components/confirmation_prompt_dialog.dart index a13bdbe6a..fc3624ebe 100644 --- a/lib/components/confirmation_prompt_dialog.dart +++ b/lib/components/confirmation_prompt_dialog.dart @@ -8,10 +8,16 @@ import 'error_snackbar.dart'; class ConfirmationPromptDialog extends AlertDialog { const ConfirmationPromptDialog({ Key? key, + required this.promptText, + required this.confirmButtonText, + required this.abortButtonText, required this.onConfirmed, required this.onAborted, }) : super(key: key); + final String promptText; + final String confirmButtonText; + final String abortButtonText; final void Function()? onConfirmed; final void Function()? onAborted; @@ -19,17 +25,17 @@ class ConfirmationPromptDialog extends AlertDialog { @override Widget build(BuildContext context) { return AlertDialog( - title: const Text("Are you sure?"), + title: Text(promptText), actions: [ TextButton( - child: const Text("CANCEL"), + child: Text(abortButtonText), onPressed: () { Navigator.of(context).pop(); onAborted?.call(); }, ), TextButton( - child: const Text("DELETE"), + child: Text(confirmButtonText), onPressed: () { Navigator.of(context).pop(); // Close the dialog onConfirmed?.call(); From b1fa54e2b3a46f8de7a48699e4f9fee83e9bad58 Mon Sep 17 00:00:00 2001 From: Chaphasilor Date: Thu, 30 Nov 2023 00:08:11 +0100 Subject: [PATCH 07/13] add proper localization for delete downloads dialog --- .../AlbumScreen/download_button.dart | 22 ++++++++++----- .../confirmation_prompt_dialog.dart | 5 +++- lib/l10n/app_en.arb | 27 +++++++++++++++++-- 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/lib/components/AlbumScreen/download_button.dart b/lib/components/AlbumScreen/download_button.dart index 9ead51584..b1e41ebf5 100644 --- a/lib/components/AlbumScreen/download_button.dart +++ b/lib/components/AlbumScreen/download_button.dart @@ -66,9 +66,19 @@ class _DownloadButtonState extends State { showDialog( context: context, builder: (context) => ConfirmationPromptDialog( - promptText: "Are you sure?", - confirmButtonText: "Delete", - abortButtonText: "Cancel", + promptText: AppLocalizations.of(context)! + .deleteDownloadsPrompt( + widget.parent.name ?? "", + widget.parent.type == "Playlist" + ? "playlist" + : "album"), + confirmButtonText: AppLocalizations.of(context)! + .deleteDownloadsConfirmButtonText( + widget.parent.type == "Playlist" + ? "playlist" + : "album"), + abortButtonText: AppLocalizations.of(context)! + .deleteDownloadsAbortButtonText, onConfirmed: () { _downloadsHelper .deleteDownloads( @@ -76,9 +86,9 @@ class _DownloadButtonState extends State { widget.items.map((e) => e.id).toList(), deletedFor: widget.parent.id) .then((value) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text("Downloads deleted."))); + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(AppLocalizations.of(context)! + .downloadsDeleted))); }).onError((error, stackTrace) { errorSnackbar(error, context); }); diff --git a/lib/components/confirmation_prompt_dialog.dart b/lib/components/confirmation_prompt_dialog.dart index fc3624ebe..ec2ec703b 100644 --- a/lib/components/confirmation_prompt_dialog.dart +++ b/lib/components/confirmation_prompt_dialog.dart @@ -25,7 +25,10 @@ class ConfirmationPromptDialog extends AlertDialog { @override Widget build(BuildContext context) { return AlertDialog( - title: Text(promptText), + title: Text( + promptText, + style: const TextStyle(fontSize: 18), + ), actions: [ TextButton( child: Text(abortButtonText), diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 6d3bbeaa2..ec9781c38 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -202,10 +202,33 @@ "@downloadErrorsTitle": {}, "noErrors": "No errors!", "@noErrors": {}, - "errorScreenError": "An error occured while getting the list of errors! At this point, you should probably just create an issue on GitHub and delete app data", + "errorScreenError": "An error occurred while getting the list of errors! At this point, you should probably just create an issue on GitHub and delete app data", "@errorScreenError": {}, "failedToGetSongFromDownloadId": "Failed to get song from download ID", "@failedToGetSongFromDownloadId": {}, + "deleteDownloadsPrompt": "Are you sure you want to delete the {itemType, select, album{album} playlist{playlist} artist{artist} other{}} '{itemName}'?", + "@deleteDownloadsPrompt": { + "placeholders": { + "itemName": { + "type": "String", + "example": "Abandon Ship" + }, + "itemType": { + "type": "String", + "example": "album" + } + } + }, + "deleteDownloadsConfirmButtonText": "Delete {itemType, select, album{album} playlist{playlist} artist{artist} other{}}", + "@deleteDownloadsConfirmButtonText": { + "placeholders": { + "itemType": { + "type": "String", + "example": "album" + } + } + }, + "deleteDownloadsAbortButtonText": "Cancel", "error": "Error", "@error": {}, "discNumber": "Disc {number}", @@ -502,4 +525,4 @@ }, "noMusicLibrariesBody": "Finamp could not find any music libraries. Please ensure that your Jellyfin server contains at least one library with the content type set to \"Music\".", "refresh": "REFRESH" -} \ No newline at end of file +} From 897519f1d165b4cad8eca26841a4155661a8aa3d Mon Sep 17 00:00:00 2001 From: Chaphasilor Date: Thu, 30 Nov 2023 00:15:56 +0100 Subject: [PATCH 08/13] use confirmation dialog for downloads screen --- .../downloaded_albums_list.dart | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/lib/components/DownloadsScreen/downloaded_albums_list.dart b/lib/components/DownloadsScreen/downloaded_albums_list.dart index 4263865dd..f81ad1930 100644 --- a/lib/components/DownloadsScreen/downloaded_albums_list.dart +++ b/lib/components/DownloadsScreen/downloaded_albums_list.dart @@ -1,4 +1,5 @@ import 'package:finamp/services/jellyfin_api_helper.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; @@ -6,6 +7,7 @@ import '../../models/finamp_models.dart'; import '../../services/downloads_helper.dart'; import '../../models/jellyfin_models.dart'; import '../album_image.dart'; +import '../confirmation_prompt_dialog.dart'; import 'item_media_source_info.dart'; import 'album_file_size.dart'; @@ -46,10 +48,30 @@ class _DownloadedAlbumsListState extends State { title: Text(album.item.name ?? "Unknown Name"), trailing: IconButton( icon: const Icon(Icons.delete), - onPressed: () async { - await deleteAlbum(context, album); - setState(() {}); - }), + onPressed: () => showDialog( + context: context, + builder: (context) => ConfirmationPromptDialog( + promptText: AppLocalizations.of(context)! + .deleteDownloadsPrompt( + album.item.name ?? "", + album.item.type == "Playlist" + ? "playlist" + : "album"), + confirmButtonText: AppLocalizations.of(context)! + .deleteDownloadsConfirmButtonText( + album.item.type == "Playlist" + ? "playlist" + : "album"), + abortButtonText: AppLocalizations.of(context)! + .deleteDownloadsAbortButtonText, + onConfirmed: () async { + await deleteAlbum(context, album); + setState(() {}); + }, + onAborted: () {}, + ), + ), + ), subtitle: AlbumFileSize( downloadedParent: album, ), From 041f276b709657ce59f806fb11f12397c6e9ce68 Mon Sep 17 00:00:00 2001 From: Chaphasilor Date: Thu, 30 Nov 2023 22:57:52 +0100 Subject: [PATCH 09/13] update download button after deleting --- .../AlbumScreen/download_button.dart | 24 ++++++++++--------- lib/services/downloads_helper.dart | 10 ++++---- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/lib/components/AlbumScreen/download_button.dart b/lib/components/AlbumScreen/download_button.dart index b1e41ebf5..5173046d7 100644 --- a/lib/components/AlbumScreen/download_button.dart +++ b/lib/components/AlbumScreen/download_button.dart @@ -79,23 +79,25 @@ class _DownloadButtonState extends State { : "album"), abortButtonText: AppLocalizations.of(context)! .deleteDownloadsAbortButtonText, - onConfirmed: () { - _downloadsHelper - .deleteDownloads( - jellyfinItemIds: - widget.items.map((e) => e.id).toList(), - deletedFor: widget.parent.id) - .then((value) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar( + onConfirmed: () async { + final messenger = ScaffoldMessenger.of(context); + try { + await _downloadsHelper.deleteDownloads( + jellyfinItemIds: + widget.items.map((e) => e.id).toList(), + deletedFor: widget.parent.id); + checkIfDownloaded(); + messenger.showSnackBar(SnackBar( content: Text(AppLocalizations.of(context)! .downloadsDeleted))); - }).onError((error, stackTrace) { + } catch (error) { errorSnackbar(error, context); - }); + } }, onAborted: () {}, ), - ).whenComplete(() => checkIfDownloaded()); + ); + // .whenComplete(() => checkIfDownloaded()); } else { if (FinampSettingsHelper .finampSettings.downloadLocationsMap.length == diff --git a/lib/services/downloads_helper.dart b/lib/services/downloads_helper.dart index de467caae..54fb5cb2b 100644 --- a/lib/services/downloads_helper.dart +++ b/lib/services/downloads_helper.dart @@ -288,15 +288,15 @@ class DownloadsHelper { shouldDeleteContent: true, )); - _downloadedItemsBox.delete(jellyfinItemId); - _downloadIdsBox.delete(downloadedSong.downloadId); + await _downloadedItemsBox.delete(jellyfinItemId); + await _downloadIdsBox.delete(downloadedSong.downloadId); if (deletedFor != null) { DownloadedParent? downloadedAlbumTemp = _downloadedParentsBox.get(deletedFor); if (downloadedAlbumTemp != null) { downloadedAlbumTemp.downloadedChildren.remove(jellyfinItemId); - _downloadedParentsBox.put(deletedFor, downloadedAlbumTemp); + await _downloadedParentsBox.put(deletedFor, downloadedAlbumTemp); } downloadedImage?.requiredBy.remove(deletedFor); @@ -346,12 +346,12 @@ class DownloadsHelper { // Loop through each directory and check if it's empty. If it is, delete the directory. if (await element.list().isEmpty) { _downloadsLogger.info("${element.path} is empty, deleting"); - element.delete(); + await element.delete(); } } if (deletedFor != null) { - _downloadedParentsBox.delete(deletedFor); + await _downloadedParentsBox.delete(deletedFor); } } catch (e) { _downloadsLogger.severe(e); From 36277cc056bdfb6442afb48c9d4f7f601a16927f Mon Sep 17 00:00:00 2001 From: Chaphasilor Date: Fri, 1 Dec 2023 17:53:38 +0100 Subject: [PATCH 10/13] add confirmation dialogs for track items on download page --- .../downloaded_albums_list.dart | 25 ++++++++++++++++--- lib/l10n/app_en.arb | 4 +-- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/lib/components/DownloadsScreen/downloaded_albums_list.dart b/lib/components/DownloadsScreen/downloaded_albums_list.dart index f81ad1930..67dc08330 100644 --- a/lib/components/DownloadsScreen/downloaded_albums_list.dart +++ b/lib/components/DownloadsScreen/downloaded_albums_list.dart @@ -109,16 +109,33 @@ class _DownloadedSongsInAlbumListState @override Widget build(BuildContext context) { return Column(children: [ + //TODO use a list builder here for (final song in widget.children) ListTile( title: Text(song.name ?? "Unknown Name"), leading: AlbumImage(item: song), trailing: IconButton( icon: const Icon(Icons.delete), - onPressed: () async { - await deleteSong(context, song); - setState(() {}); - }), + onPressed: () => showDialog( + context: context, + builder: (context) => ConfirmationPromptDialog( + promptText: AppLocalizations.of(context)! + .deleteDownloadsPrompt( + song.name ?? "", + "track"), + confirmButtonText: AppLocalizations.of(context)! + .deleteDownloadsConfirmButtonText( + "track"), + abortButtonText: AppLocalizations.of(context)! + .deleteDownloadsAbortButtonText, + onConfirmed: () async { + await deleteSong(context, song); + setState(() {}); + }, + onAborted: () {}, + ), + ), + ), subtitle: ItemMediaSourceInfo( songId: song.id, ), diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index ec9781c38..eaf4ec39e 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -206,7 +206,7 @@ "@errorScreenError": {}, "failedToGetSongFromDownloadId": "Failed to get song from download ID", "@failedToGetSongFromDownloadId": {}, - "deleteDownloadsPrompt": "Are you sure you want to delete the {itemType, select, album{album} playlist{playlist} artist{artist} other{}} '{itemName}'?", + "deleteDownloadsPrompt": "Are you sure you want to delete the {itemType, select, album{album} playlist{playlist} artist{artist} track{song} other{}} '{itemName}'?", "@deleteDownloadsPrompt": { "placeholders": { "itemName": { @@ -219,7 +219,7 @@ } } }, - "deleteDownloadsConfirmButtonText": "Delete {itemType, select, album{album} playlist{playlist} artist{artist} other{}}", + "deleteDownloadsConfirmButtonText": "Delete {itemType, select, album{album} playlist{playlist} artist{artist} track{song} other{}}", "@deleteDownloadsConfirmButtonText": { "placeholders": { "itemType": { From 4798f839ea5e314f9872d79e3cd6a334f002daa6 Mon Sep 17 00:00:00 2001 From: Chaphasilor Date: Fri, 1 Dec 2023 18:34:16 +0100 Subject: [PATCH 11/13] add confirmation dialog when deleting artists/genres --- .../ArtistScreen/artist_download_button.dart | 63 +++++++++++++------ .../confirmation_prompt_dialog.dart | 46 ++++++++++---- lib/l10n/app_en.arb | 4 +- 3 files changed, 81 insertions(+), 32 deletions(-) diff --git a/lib/components/ArtistScreen/artist_download_button.dart b/lib/components/ArtistScreen/artist_download_button.dart index ce701863f..bfc5ba5a1 100644 --- a/lib/components/ArtistScreen/artist_download_button.dart +++ b/lib/components/ArtistScreen/artist_download_button.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:get_it/get_it.dart'; import 'package:hive/hive.dart'; @@ -9,6 +10,7 @@ import '../../services/finamp_user_helper.dart'; import '../../services/jellyfin_api_helper.dart'; import '../../services/downloads_helper.dart'; import '../AlbumScreen/download_dialog.dart'; +import '../confirmation_prompt_dialog.dart'; import '../error_snackbar.dart'; class ArtistDownloadButton extends StatefulWidget { @@ -46,6 +48,7 @@ class _ArtistDownloadButtonState extends State { valueListenable: FinampSettingsHelper.finampSettingsListener, builder: (context, box, _) { final isOffline = box.get("FinampSettings")?.isOffline ?? false; + bool deleteAlbums = false; if (isOffline) { return _disabledButton; @@ -62,28 +65,52 @@ class _ArtistDownloadButtonState extends State { if (snapshot.hasData) { final undownloadedAlbums = _getUndownloadedAlbums(snapshot.data!); + deleteAlbums = undownloadedAlbums.isEmpty; return IconButton( - icon: undownloadedAlbums.isEmpty + icon: deleteAlbums ? const Icon(Icons.delete) : const Icon(Icons.download), - onPressed: () async { - if (undownloadedAlbums.isEmpty) { - final deleteFutures = snapshot.data!.map((e) => - _downloadsHelper.deleteDownloads( - jellyfinItemIds: _downloadsHelper - .getDownloadedParent(e.id)! - .downloadedChildren - .keys - .toList(), - deletedFor: e.id)); - Future.wait(deleteFutures).then((_) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text("Downloads deleted."))); - }, - onError: (error, stackTrace) => - errorSnackbar(error, context)); + onPressed:() async { + if (deleteAlbums) { + showDialog( + context: context, + builder: (context) => ConfirmationPromptDialog( + promptText: AppLocalizations.of(context)! + .deleteDownloadsPrompt( + widget.artist.name ?? "", + "artist"), + confirmButtonText: AppLocalizations.of(context)! + .deleteDownloadsConfirmButtonText( + "artist"), + abortButtonText: AppLocalizations.of(context)! + .deleteDownloadsAbortButtonText, + onConfirmed: () async { + try { + final deleteFutures = snapshot.data!.map((e) => + _downloadsHelper.deleteDownloads( + jellyfinItemIds: _downloadsHelper + .getDownloadedParent(e.id)! + .downloadedChildren + .keys + .toList(), + deletedFor: e.id)); + await Future.wait(deleteFutures); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text("Downloads deleted."))); + final undownloadedAlbums = + _getUndownloadedAlbums(snapshot.data!); + setState(() { + deleteAlbums = undownloadedAlbums.isEmpty; + }); + } catch (error) { + errorSnackbar(error, context); + } + }, + onAborted: () {}, + ), + ); } else { List?>> albumInfoFutures = []; for (var element in undownloadedAlbums) { diff --git a/lib/components/confirmation_prompt_dialog.dart b/lib/components/confirmation_prompt_dialog.dart index ec2ec703b..e1b40888b 100644 --- a/lib/components/confirmation_prompt_dialog.dart +++ b/lib/components/confirmation_prompt_dialog.dart @@ -25,24 +25,46 @@ class ConfirmationPromptDialog extends AlertDialog { @override Widget build(BuildContext context) { return AlertDialog( + buttonPadding: const EdgeInsets.all(0.0), + contentPadding: const EdgeInsets.all(0.0), + insetPadding: const EdgeInsets.all(32.0), + actionsPadding: const EdgeInsets.all(0.0), + actionsAlignment: MainAxisAlignment.spaceAround, + actionsOverflowAlignment: OverflowBarAlignment.center, + actionsOverflowDirection: VerticalDirection.up, title: Text( promptText, style: const TextStyle(fontSize: 18), ), actions: [ - TextButton( - child: Text(abortButtonText), - onPressed: () { - Navigator.of(context).pop(); - onAborted?.call(); - }, + Container( + constraints: const BoxConstraints( + maxWidth: 150.0, + ), + child: TextButton( + child: Text(abortButtonText, + textAlign: TextAlign.center, + ), + onPressed: () { + Navigator.of(context).pop(); + onAborted?.call(); + }, + ), ), - TextButton( - child: Text(confirmButtonText), - onPressed: () { - Navigator.of(context).pop(); // Close the dialog - onConfirmed?.call(); - }, + Container( + constraints: const BoxConstraints( + maxWidth: 150.0, + ), + child: TextButton( + child: Text(confirmButtonText, + textAlign: TextAlign.center, + softWrap: true, + ), + onPressed: () { + Navigator.of(context).pop(); // Close the dialog + onConfirmed?.call(); + }, + ), ), ], ); diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index eaf4ec39e..dffef043d 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -206,7 +206,7 @@ "@errorScreenError": {}, "failedToGetSongFromDownloadId": "Failed to get song from download ID", "@failedToGetSongFromDownloadId": {}, - "deleteDownloadsPrompt": "Are you sure you want to delete the {itemType, select, album{album} playlist{playlist} artist{artist} track{song} other{}} '{itemName}'?", + "deleteDownloadsPrompt": "Are you sure you want to delete the {itemType, select, album{album} playlist{playlist} artist{artist} track{song} other{}} '{itemName}' from this device?", "@deleteDownloadsPrompt": { "placeholders": { "itemName": { @@ -219,7 +219,7 @@ } } }, - "deleteDownloadsConfirmButtonText": "Delete {itemType, select, album{album} playlist{playlist} artist{artist} track{song} other{}}", + "deleteDownloadsConfirmButtonText": "Delete {itemType, select, album{album} playlist{playlist} artist{artist} track{song} other{}} from device", "@deleteDownloadsConfirmButtonText": { "placeholders": { "itemType": { From 9d3fd49ec9b81f4cc9b1a17a08cb93280e1ea057 Mon Sep 17 00:00:00 2001 From: Chaphasilor Date: Mon, 4 Dec 2023 18:14:58 +0100 Subject: [PATCH 12/13] differentiate artists from genres --- lib/components/ArtistScreen/artist_download_button.dart | 4 ++-- lib/l10n/app_en.arb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/components/ArtistScreen/artist_download_button.dart b/lib/components/ArtistScreen/artist_download_button.dart index bfc5ba5a1..554fa05c8 100644 --- a/lib/components/ArtistScreen/artist_download_button.dart +++ b/lib/components/ArtistScreen/artist_download_button.dart @@ -79,10 +79,10 @@ class _ArtistDownloadButtonState extends State { promptText: AppLocalizations.of(context)! .deleteDownloadsPrompt( widget.artist.name ?? "", - "artist"), + widget.artist.type == "MusicArtist" ? "artist" : "genre"), confirmButtonText: AppLocalizations.of(context)! .deleteDownloadsConfirmButtonText( - "artist"), + widget.artist.type == "MusicArtist" ? "artist" : "genre"), abortButtonText: AppLocalizations.of(context)! .deleteDownloadsAbortButtonText, onConfirmed: () async { diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index dffef043d..d19126808 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -206,7 +206,7 @@ "@errorScreenError": {}, "failedToGetSongFromDownloadId": "Failed to get song from download ID", "@failedToGetSongFromDownloadId": {}, - "deleteDownloadsPrompt": "Are you sure you want to delete the {itemType, select, album{album} playlist{playlist} artist{artist} track{song} other{}} '{itemName}' from this device?", + "deleteDownloadsPrompt": "Are you sure you want to delete the {itemType, select, album{album} playlist{playlist} artist{artist} genre{genre} track{song} other{}} '{itemName}' from this device?", "@deleteDownloadsPrompt": { "placeholders": { "itemName": { @@ -219,7 +219,7 @@ } } }, - "deleteDownloadsConfirmButtonText": "Delete {itemType, select, album{album} playlist{playlist} artist{artist} track{song} other{}} from device", + "deleteDownloadsConfirmButtonText": "Delete {itemType, select, album{album} playlist{playlist} artist{artist} genre{genre} track{song} other{}} from device", "@deleteDownloadsConfirmButtonText": { "placeholders": { "itemType": { From 9db368c3ab28a2ef7437c3acb3b876b10debfb69 Mon Sep 17 00:00:00 2001 From: Chaphasilor Date: Mon, 4 Dec 2023 18:22:03 +0100 Subject: [PATCH 13/13] simplify delete button string --- lib/components/AlbumScreen/download_button.dart | 5 +---- .../ArtistScreen/artist_download_button.dart | 3 +-- .../DownloadsScreen/downloaded_albums_list.dart | 8 ++------ lib/l10n/app_en.arb | 12 ++++-------- 4 files changed, 8 insertions(+), 20 deletions(-) diff --git a/lib/components/AlbumScreen/download_button.dart b/lib/components/AlbumScreen/download_button.dart index 5173046d7..a077f91e0 100644 --- a/lib/components/AlbumScreen/download_button.dart +++ b/lib/components/AlbumScreen/download_button.dart @@ -73,10 +73,7 @@ class _DownloadButtonState extends State { ? "playlist" : "album"), confirmButtonText: AppLocalizations.of(context)! - .deleteDownloadsConfirmButtonText( - widget.parent.type == "Playlist" - ? "playlist" - : "album"), + .deleteDownloadsConfirmButtonText, abortButtonText: AppLocalizations.of(context)! .deleteDownloadsAbortButtonText, onConfirmed: () async { diff --git a/lib/components/ArtistScreen/artist_download_button.dart b/lib/components/ArtistScreen/artist_download_button.dart index 554fa05c8..e30f376a9 100644 --- a/lib/components/ArtistScreen/artist_download_button.dart +++ b/lib/components/ArtistScreen/artist_download_button.dart @@ -81,8 +81,7 @@ class _ArtistDownloadButtonState extends State { widget.artist.name ?? "", widget.artist.type == "MusicArtist" ? "artist" : "genre"), confirmButtonText: AppLocalizations.of(context)! - .deleteDownloadsConfirmButtonText( - widget.artist.type == "MusicArtist" ? "artist" : "genre"), + .deleteDownloadsConfirmButtonText, abortButtonText: AppLocalizations.of(context)! .deleteDownloadsAbortButtonText, onConfirmed: () async { diff --git a/lib/components/DownloadsScreen/downloaded_albums_list.dart b/lib/components/DownloadsScreen/downloaded_albums_list.dart index 67dc08330..9922386f8 100644 --- a/lib/components/DownloadsScreen/downloaded_albums_list.dart +++ b/lib/components/DownloadsScreen/downloaded_albums_list.dart @@ -58,10 +58,7 @@ class _DownloadedAlbumsListState extends State { ? "playlist" : "album"), confirmButtonText: AppLocalizations.of(context)! - .deleteDownloadsConfirmButtonText( - album.item.type == "Playlist" - ? "playlist" - : "album"), + .deleteDownloadsConfirmButtonText, abortButtonText: AppLocalizations.of(context)! .deleteDownloadsAbortButtonText, onConfirmed: () async { @@ -124,8 +121,7 @@ class _DownloadedSongsInAlbumListState song.name ?? "", "track"), confirmButtonText: AppLocalizations.of(context)! - .deleteDownloadsConfirmButtonText( - "track"), + .deleteDownloadsConfirmButtonText, abortButtonText: AppLocalizations.of(context)! .deleteDownloadsAbortButtonText, onConfirmed: () async { diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index d19126808..1a4f73a80 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -217,16 +217,12 @@ "type": "String", "example": "album" } - } + }, + "description": "Confirmation prompt shown before deleting downloaded media from the local device, destructive action, doesn't affect the media on the server." }, - "deleteDownloadsConfirmButtonText": "Delete {itemType, select, album{album} playlist{playlist} artist{artist} genre{genre} track{song} other{}} from device", + "deleteDownloadsConfirmButtonText": "Delete", "@deleteDownloadsConfirmButtonText": { - "placeholders": { - "itemType": { - "type": "String", - "example": "album" - } - } + "description": "Shown in the confirmation dialog for deleting downloaded media from the local device." }, "deleteDownloadsAbortButtonText": "Cancel", "error": "Error",