From 2dd84e7c5605b9a942a5c4a812a8491a25d9d7d0 Mon Sep 17 00:00:00 2001 From: Jonas Resch Date: Mon, 10 Jun 2024 14:18:52 +0200 Subject: [PATCH 1/4] Fix: App crashes on second open when local address empty --- .../main/java/com/cappielloantonio/tempo/util/Preferences.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/cappielloantonio/tempo/util/Preferences.kt b/app/src/main/java/com/cappielloantonio/tempo/util/Preferences.kt index 5cbe9035..253663e5 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/util/Preferences.kt +++ b/app/src/main/java/com/cappielloantonio/tempo/util/Preferences.kt @@ -175,7 +175,7 @@ object Preferences { @JvmStatic fun isInUseServerAddressLocal(): Boolean { - return getInUseServerAddress() == getLocalAddress() + return getInUseServerAddress() == getLocalAddress() && !getLocalAddress().isNullOrEmpty() } @JvmStatic -- 2.49.1 From 8d24659bd794d7c22b414df0003fbc815e0c4cb7 Mon Sep 17 00:00:00 2001 From: Jonas Resch Date: Mon, 10 Jun 2024 17:59:48 +0200 Subject: [PATCH 2/4] Add to playlist: check if song already exists in playlist and display a dialog if it does --- .../tempo/repository/PlaylistRepository.java | 23 ++++++ .../ui/dialog/PlaylistChooserDialog.java | 24 ++++++- .../dialog/PlaylistDuplicateSongDialog.java | 70 +++++++++++++++++++ .../viewmodel/PlaylistChooserViewModel.java | 7 ++ .../layout/dialog_playlist_duplicate_song.xml | 15 ++++ app/src/main/res/values-de/strings.xml | 4 ++ app/src/main/res/values/strings.xml | 4 ++ 7 files changed, 144 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PlaylistDuplicateSongDialog.java create mode 100644 app/src/main/res/layout/dialog_playlist_duplicate_song.xml diff --git a/app/src/main/java/com/cappielloantonio/tempo/repository/PlaylistRepository.java b/app/src/main/java/com/cappielloantonio/tempo/repository/PlaylistRepository.java index bcf0c732..ee378c93 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/repository/PlaylistRepository.java +++ b/app/src/main/java/com/cappielloantonio/tempo/repository/PlaylistRepository.java @@ -73,6 +73,29 @@ public class PlaylistRepository { return listLivePlaylistSongs; } + public MutableLiveData isSongInPlaylist(String playlistId, String songId) { + MutableLiveData songInPlayList = new MutableLiveData<>(); + + App.getSubsonicClientInstance(false) + .getPlaylistClient() + .getPlaylist(playlistId) + .enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getPlaylist() != null && response.body().getSubsonicResponse().getPlaylist().getEntries() != null) { + List songs = response.body().getSubsonicResponse().getPlaylist().getEntries(); + songInPlayList.setValue(songs.stream().anyMatch(s -> s.getId().equals(songId))); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + } + }); + + return songInPlayList; + } + public void addSongToPlaylist(String playlistId, ArrayList songsId) { App.getSubsonicClientInstance(false) .getPlaylistClient() diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PlaylistChooserDialog.java b/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PlaylistChooserDialog.java index f0266c84..f8459b11 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PlaylistChooserDialog.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PlaylistChooserDialog.java @@ -12,6 +12,7 @@ import androidx.recyclerview.widget.LinearLayoutManager; import com.cappielloantonio.tempo.R; import com.cappielloantonio.tempo.databinding.DialogPlaylistChooserBinding; import com.cappielloantonio.tempo.interfaces.ClickCallback; +import com.cappielloantonio.tempo.interfaces.DialogClickCallback; import com.cappielloantonio.tempo.subsonic.models.Playlist; import com.cappielloantonio.tempo.ui.adapter.PlaylistDialogHorizontalAdapter; import com.cappielloantonio.tempo.util.Constants; @@ -36,7 +37,8 @@ public class PlaylistChooserDialog extends DialogFragment implements ClickCallba return new MaterialAlertDialogBuilder(getActivity()) .setView(bind.getRoot()) .setTitle(R.string.playlist_chooser_dialog_title) - .setNeutralButton(R.string.playlist_chooser_dialog_neutral_button, (dialog, id) -> { }) + .setNeutralButton(R.string.playlist_chooser_dialog_neutral_button, (dialog, id) -> { + }) .setNegativeButton(R.string.playlist_chooser_dialog_negative_button, (dialog, id) -> dialog.cancel()) .create(); } @@ -98,7 +100,23 @@ public class PlaylistChooserDialog extends DialogFragment implements ClickCallba @Override public void onPlaylistClick(Bundle bundle) { Playlist playlist = bundle.getParcelable(Constants.PLAYLIST_OBJECT); - playlistChooserViewModel.addSongToPlaylist(playlist.getId()); - dismiss(); + + playlistChooserViewModel.isSongInPlaylist(Objects.requireNonNull(playlist).getId(), requireActivity()).observe(requireActivity(), songInPlaylist -> { + if (songInPlaylist) { + playlistChooserViewModel.addSongToPlaylist(playlist.getId()); + dismiss(); + } else { + PlaylistDuplicateSongDialog playlistDuplicateSongDialog = new PlaylistDuplicateSongDialog(playlist, + playlistChooserViewModel.getSongToAdd(), + new DialogClickCallback() { + @Override + public void onPositiveClick() { + playlistChooserViewModel.addSongToPlaylist(playlist.getId()); + dismiss(); + } + }); + playlistDuplicateSongDialog.show(getActivity().getSupportFragmentManager(), null); + } + }); } } diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PlaylistDuplicateSongDialog.java b/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PlaylistDuplicateSongDialog.java new file mode 100644 index 00000000..d1f07d01 --- /dev/null +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PlaylistDuplicateSongDialog.java @@ -0,0 +1,70 @@ +package com.cappielloantonio.tempo.ui.dialog; + +import android.app.Dialog; +import android.os.Bundle; +import android.widget.Button; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; + +import com.cappielloantonio.tempo.R; +import com.cappielloantonio.tempo.databinding.DialogPlaylistDuplicateSongBinding; +import com.cappielloantonio.tempo.interfaces.DialogClickCallback; +import com.cappielloantonio.tempo.subsonic.models.Child; +import com.cappielloantonio.tempo.subsonic.models.Playlist; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +public class PlaylistDuplicateSongDialog extends DialogFragment { + + private final Playlist playlist; + + private final Child song; + + private final DialogClickCallback dialogClickCallback; + + public PlaylistDuplicateSongDialog(Playlist playlist, Child song, DialogClickCallback dialogClickCallback) { + this.playlist = playlist; + this.song = song; + this.dialogClickCallback = dialogClickCallback; + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + DialogPlaylistDuplicateSongBinding bind = DialogPlaylistDuplicateSongBinding.inflate(getLayoutInflater()); + bind.playlistDuplicateSongDialogSummary + .setText(getResources().getString(R.string.playlist_duplicate_song_dialog_summary, song.getTitle(), playlist.getName())); + + return new MaterialAlertDialogBuilder(requireContext()) + .setView(bind.getRoot()) + .setTitle(R.string.playlist_duplicate_song_dialog_title) + .setPositiveButton(R.string.playlist_duplicate_song_dialog_positive_button, null) + .setNegativeButton(R.string.playlist_duplicate_song_dialog_negative_button, null) + .create(); + } + + @Override + public void onResume() { + super.onResume(); + setButtonAction(); + } + + private void setButtonAction() { + androidx.appcompat.app.AlertDialog dialog = (androidx.appcompat.app.AlertDialog) getDialog(); + + if (dialog != null) { + Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE); + positiveButton.setOnClickListener(v -> { + dialogClickCallback.onPositiveClick(); + dialog.dismiss(); + }); + + Button negativeButton = dialog.getButton(Dialog.BUTTON_NEGATIVE); + negativeButton.setOnClickListener(v -> { + dialogClickCallback.onNegativeClick(); + dialog.dismiss(); + }); + } + } +} diff --git a/app/src/main/java/com/cappielloantonio/tempo/viewmodel/PlaylistChooserViewModel.java b/app/src/main/java/com/cappielloantonio/tempo/viewmodel/PlaylistChooserViewModel.java index cb8833fe..bb74100b 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/viewmodel/PlaylistChooserViewModel.java +++ b/app/src/main/java/com/cappielloantonio/tempo/viewmodel/PlaylistChooserViewModel.java @@ -33,6 +33,13 @@ public class PlaylistChooserViewModel extends AndroidViewModel { return playlists; } + public LiveData isSongInPlaylist(String playlistId, LifecycleOwner owner) { + MutableLiveData songs = new MutableLiveData<>(); + + playlistRepository.isSongInPlaylist(playlistId, toAdd.getId()).observe(owner, songs::postValue); + return songs; + } + public void addSongToPlaylist(String playlistId) { playlistRepository.addSongToPlaylist(playlistId, new ArrayList(Collections.singletonList(toAdd.getId()))); } diff --git a/app/src/main/res/layout/dialog_playlist_duplicate_song.xml b/app/src/main/res/layout/dialog_playlist_duplicate_song.xml new file mode 100644 index 00000000..0cddd1c5 --- /dev/null +++ b/app/src/main/res/layout/dialog_playlist_duplicate_song.xml @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 5c627b9a..7138fde7 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -406,4 +406,8 @@ Tempo wird den Server bitten, die Datei zu transkodieren. Der vom Benutzer gewünschte Codec ist %1$s, die Bitrate wird dieselbe wie bei der original Datei sein. Die potentielle Transkodierung der Datei in das gewünschte Format ist vom Server abhängig. Dieser kann die Operation gegebenenfalls nicht unterstützen. Tempo wird den Server bitten, die Bitrate der Datei zu erändern. Die vom Benutzer gewünschte Bitrate ist %1$s, der Codec der Originaldatei wird nicht verändert. Änderungen an der Bitrate der Datei werden vom Server ausgeführt, dieser kann die Operation gegebenenfalls nicht unterstützen. Die Anwendung wird den Server bitten die Datei zu transkodieren und die Bitrate zu verändern. Der vom Benutzer gewünschte Codec ist %1$s, mit der Bitrate %2$s. Änderungen am Codec und an der Bitrate der Datei werden vom Server ausgeführt, dieser kann die Operation gegebenenfalls nicht unterstützen. + Doppelter Track + Der Track \"%1$s\" existiert bereits in der Playlist \"%2$s\" + Trotzdem hinzufügen + Nicht hinzufügen \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5fd1ac33..c3c1c763 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -415,4 +415,8 @@ unDraw A special thanks goes to unDraw without whose illustrations we could not have made this application more beautiful. https://undraw.co/ + Duplicate song + The song \"%1$s\" already exists in playlist \"%2$s\". + Add anyway + Don\'t add -- 2.49.1 From e2f82ccd12e657a0dc92d16684ce396347c9e621 Mon Sep 17 00:00:00 2001 From: Jonas Resch Date: Mon, 10 Jun 2024 18:07:53 +0200 Subject: [PATCH 3/4] Revert code format change --- .../tempo/ui/dialog/PlaylistChooserDialog.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PlaylistChooserDialog.java b/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PlaylistChooserDialog.java index f8459b11..4eac9e6d 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PlaylistChooserDialog.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PlaylistChooserDialog.java @@ -37,8 +37,7 @@ public class PlaylistChooserDialog extends DialogFragment implements ClickCallba return new MaterialAlertDialogBuilder(getActivity()) .setView(bind.getRoot()) .setTitle(R.string.playlist_chooser_dialog_title) - .setNeutralButton(R.string.playlist_chooser_dialog_neutral_button, (dialog, id) -> { - }) + .setNeutralButton(R.string.playlist_chooser_dialog_neutral_button, (dialog, id) -> { }) .setNegativeButton(R.string.playlist_chooser_dialog_negative_button, (dialog, id) -> dialog.cancel()) .create(); } -- 2.49.1 From 92ebba1d4433d767b763f16f47330cb5e5c72a2a Mon Sep 17 00:00:00 2001 From: Anna Teier Date: Sun, 7 Dec 2025 20:54:46 +0100 Subject: [PATCH 4/4] fix: isSonginPlayList condition --- .../cappielloantonio/tempo/ui/dialog/PlaylistChooserDialog.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PlaylistChooserDialog.java b/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PlaylistChooserDialog.java index 4eac9e6d..9c4609c7 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PlaylistChooserDialog.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PlaylistChooserDialog.java @@ -101,7 +101,7 @@ public class PlaylistChooserDialog extends DialogFragment implements ClickCallba Playlist playlist = bundle.getParcelable(Constants.PLAYLIST_OBJECT); playlistChooserViewModel.isSongInPlaylist(Objects.requireNonNull(playlist).getId(), requireActivity()).observe(requireActivity(), songInPlaylist -> { - if (songInPlaylist) { + if (!songInPlaylist) { playlistChooserViewModel.addSongToPlaylist(playlist.getId()); dismiss(); } else { -- 2.49.1