5 Commits

Author SHA1 Message Date
92ebba1d44 fix: isSonginPlayList condition 2025-12-07 20:54:46 +01:00
Jonas Resch
e2f82ccd12 Revert code format change 2025-12-07 20:46:39 +01:00
Jonas Resch
8d24659bd7 Add to playlist: check if song already exists in playlist and display a dialog if it does 2025-12-07 20:46:39 +01:00
Jonas Resch
2dd84e7c56 Fix: App crashes on second open when local address empty 2025-12-07 20:46:39 +01:00
5aa82bec70 fix: add compileSdk again 2025-12-07 20:38:19 +01:00
9 changed files with 145 additions and 3 deletions

View File

@@ -5,6 +5,8 @@ apply plugin: 'kotlin-parcelize'
android { android {
buildToolsVersion = '35.0.0' buildToolsVersion = '35.0.0'
compileSdk 35
defaultConfig { defaultConfig {
minSdkVersion 24 minSdkVersion 24
targetSdk 35 targetSdk 35

View File

@@ -73,6 +73,29 @@ public class PlaylistRepository {
return listLivePlaylistSongs; return listLivePlaylistSongs;
} }
public MutableLiveData<Boolean> isSongInPlaylist(String playlistId, String songId) {
MutableLiveData<Boolean> songInPlayList = new MutableLiveData<>();
App.getSubsonicClientInstance(false)
.getPlaylistClient()
.getPlaylist(playlistId)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getPlaylist() != null && response.body().getSubsonicResponse().getPlaylist().getEntries() != null) {
List<Child> songs = response.body().getSubsonicResponse().getPlaylist().getEntries();
songInPlayList.setValue(songs.stream().anyMatch(s -> s.getId().equals(songId)));
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
}
});
return songInPlayList;
}
public void addSongToPlaylist(String playlistId, ArrayList<String> songsId) { public void addSongToPlaylist(String playlistId, ArrayList<String> songsId) {
App.getSubsonicClientInstance(false) App.getSubsonicClientInstance(false)
.getPlaylistClient() .getPlaylistClient()

View File

@@ -12,6 +12,7 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R; import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogPlaylistChooserBinding; import com.cappielloantonio.tempo.databinding.DialogPlaylistChooserBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback; import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.interfaces.DialogClickCallback;
import com.cappielloantonio.tempo.subsonic.models.Playlist; import com.cappielloantonio.tempo.subsonic.models.Playlist;
import com.cappielloantonio.tempo.ui.adapter.PlaylistDialogHorizontalAdapter; import com.cappielloantonio.tempo.ui.adapter.PlaylistDialogHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants; import com.cappielloantonio.tempo.util.Constants;
@@ -98,7 +99,23 @@ public class PlaylistChooserDialog extends DialogFragment implements ClickCallba
@Override @Override
public void onPlaylistClick(Bundle bundle) { public void onPlaylistClick(Bundle bundle) {
Playlist playlist = bundle.getParcelable(Constants.PLAYLIST_OBJECT); 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);
}
});
} }
} }

View File

@@ -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();
});
}
}
}

View File

@@ -175,7 +175,7 @@ object Preferences {
@JvmStatic @JvmStatic
fun isInUseServerAddressLocal(): Boolean { fun isInUseServerAddressLocal(): Boolean {
return getInUseServerAddress() == getLocalAddress() return getInUseServerAddress() == getLocalAddress() && !getLocalAddress().isNullOrEmpty()
} }
@JvmStatic @JvmStatic

View File

@@ -33,6 +33,13 @@ public class PlaylistChooserViewModel extends AndroidViewModel {
return playlists; return playlists;
} }
public LiveData<Boolean> isSongInPlaylist(String playlistId, LifecycleOwner owner) {
MutableLiveData<Boolean> songs = new MutableLiveData<>();
playlistRepository.isSongInPlaylist(playlistId, toAdd.getId()).observe(owner, songs::postValue);
return songs;
}
public void addSongToPlaylist(String playlistId) { public void addSongToPlaylist(String playlistId) {
playlistRepository.addSongToPlaylist(playlistId, new ArrayList(Collections.singletonList(toAdd.getId()))); playlistRepository.addSongToPlaylist(playlistId, new ArrayList(Collections.singletonList(toAdd.getId())));
} }

View File

@@ -0,0 +1,15 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/playlist_duplicate_song_dialog_summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="4dp" />
</LinearLayout>

View File

@@ -406,4 +406,8 @@
<string name="track_info_summary_transcoding_codec">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.</string> <string name="track_info_summary_transcoding_codec">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.</string>
<string name="track_info_summary_transcoding_bitrate">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.</string> <string name="track_info_summary_transcoding_bitrate">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.</string>
<string name="track_info_summary_full_transcode">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.</string> <string name="track_info_summary_full_transcode">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.</string>
<string name="playlist_duplicate_song_dialog_title">Doppelter Track</string>
<string name="playlist_duplicate_song_dialog_summary">Der Track \"%1$s\" existiert bereits in der Playlist \"%2$s\"</string>
<string name="playlist_duplicate_song_dialog_positive_button">Trotzdem hinzufügen</string>
<string name="playlist_duplicate_song_dialog_negative_button">Nicht hinzufügen</string>
</resources> </resources>

View File

@@ -415,4 +415,8 @@
<string name="undraw_page">unDraw</string> <string name="undraw_page">unDraw</string>
<string name="undraw_thanks">A special thanks goes to unDraw without whose illustrations we could not have made this application more beautiful.</string> <string name="undraw_thanks">A special thanks goes to unDraw without whose illustrations we could not have made this application more beautiful.</string>
<string name="undraw_url">https://undraw.co/</string> <string name="undraw_url">https://undraw.co/</string>
<string name="playlist_duplicate_song_dialog_title">Duplicate song</string>
<string name="playlist_duplicate_song_dialog_summary">The song \"%1$s\" already exists in playlist \"%2$s\".</string>
<string name="playlist_duplicate_song_dialog_positive_button">Add anyway</string>
<string name="playlist_duplicate_song_dialog_negative_button">Don\'t add</string>
</resources> </resources>