mirror of
https://github.com/antebudimir/tempus.git
synced 2026-04-15 16:27:26 +00:00
Port remove song of playlist from tempus ng (#457)
* feat: implement track removal from playlists with real-time UI updates - Added 'Remove from playlist' option to song bottom sheet (appears only when inside a playlist) - Implemented immediate UI refresh for track count and duration in playlist header - Fixed a bug where shuffling for covers scrambled the actual playlist song order - Improved PlaylistPageViewModel to clear stale data and handle isolated updates correctly - Added dedicated success/failure messages for track removal in English and Italian - Unified heart icon size to 14dp across all track list items * fix: missing code from port process The cherry-pick was missing the database getter and the function to remove a song from a playlist --------- Co-authored-by: beeetfarmer <176325048+beeetfarmer@users.noreply.github.com>
This commit is contained in:
parent
b403d69982
commit
4f8212d491
11 changed files with 256 additions and 20 deletions
|
|
@ -19,6 +19,9 @@ public interface PlaylistDao {
|
|||
@Query("SELECT * FROM playlist")
|
||||
LiveData<List<Playlist>> getAll();
|
||||
|
||||
@Query("SELECT * FROM playlist")
|
||||
List<Playlist> getAllSync();
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
void insert(Playlist playlist);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,11 @@ package com.cappielloantonio.tempo.repository;
|
|||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.R;
|
||||
|
|
@ -23,8 +26,45 @@ import retrofit2.Callback;
|
|||
import retrofit2.Response;
|
||||
|
||||
public class PlaylistRepository {
|
||||
private static final MutableLiveData<Boolean> playlistUpdateTrigger = new MutableLiveData<>();
|
||||
|
||||
public LiveData<Boolean> getPlaylistUpdateTrigger() {
|
||||
return playlistUpdateTrigger;
|
||||
}
|
||||
|
||||
public void notifyPlaylistChanged() {
|
||||
playlistUpdateTrigger.postValue(true);
|
||||
refreshAllPlaylists();
|
||||
}
|
||||
|
||||
@androidx.media3.common.util.UnstableApi
|
||||
private final PlaylistDao playlistDao = AppDatabase.getInstance().playlistDao();
|
||||
private static final MutableLiveData<List<Playlist>> allPlaylistsLiveData = new MutableLiveData<>();
|
||||
|
||||
public LiveData<List<Playlist>> getAllPlaylists(LifecycleOwner owner) {
|
||||
refreshAllPlaylists();
|
||||
return allPlaylistsLiveData;
|
||||
}
|
||||
|
||||
public void refreshAllPlaylists() {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getPlaylistClient()
|
||||
.getPlaylists()
|
||||
.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().getPlaylists() != null) {
|
||||
List<Playlist> playlists = response.body().getSubsonicResponse().getPlaylists().getPlaylists();
|
||||
allPlaylistsLiveData.postValue(playlists);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public MutableLiveData<List<Playlist>> getPlaylists(boolean random, int size) {
|
||||
MutableLiveData<List<Playlist>> listLivePlaylists = new MutableLiveData<>(new ArrayList<>());
|
||||
|
||||
|
|
@ -104,9 +144,16 @@ public class PlaylistRepository {
|
|||
return playlistLiveData;
|
||||
}
|
||||
|
||||
public void addSongToPlaylist(String playlistId, ArrayList<String> songsId, Boolean playlistVisibilityIsPublic) {
|
||||
public interface AddToPlaylistCallback {
|
||||
void onSuccess();
|
||||
void onFailure();
|
||||
void onAllSkipped();
|
||||
}
|
||||
|
||||
public void addSongToPlaylist(String playlistId, ArrayList<String> songsId, Boolean playlistVisibilityIsPublic, AddToPlaylistCallback callback) {
|
||||
android.util.Log.d("PlaylistRepository", "addSongToPlaylist: id=" + playlistId + ", songs=" + songsId);
|
||||
if (songsId.isEmpty()) {
|
||||
Toast.makeText(App.getContext(), App.getContext().getString(R.string.playlist_chooser_dialog_toast_all_skipped), Toast.LENGTH_SHORT).show();
|
||||
if (callback != null) callback.onAllSkipped();
|
||||
} else{
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getPlaylistClient()
|
||||
|
|
@ -114,17 +161,45 @@ public class PlaylistRepository {
|
|||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
Toast.makeText(App.getContext(), App.getContext().getString(R.string.playlist_chooser_dialog_toast_add_success), Toast.LENGTH_SHORT).show();
|
||||
if (response.isSuccessful()) notifyPlaylistChanged();
|
||||
if (callback != null) callback.onSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
Toast.makeText(App.getContext(), App.getContext().getString(R.string.playlist_chooser_dialog_toast_add_failure), Toast.LENGTH_SHORT).show();
|
||||
if (callback != null) callback.onFailure();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void removeSongFromPlaylist(String playlistId, int index, AddToPlaylistCallback callback) {
|
||||
ArrayList<Integer> indexes = new ArrayList<>();
|
||||
indexes.add(index);
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getPlaylistClient()
|
||||
.updatePlaylist(playlistId, null, true, null, indexes)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful()) notifyPlaylistChanged();
|
||||
if (callback != null) {
|
||||
if (response.isSuccessful()) callback.onSuccess();
|
||||
else callback.onFailure();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
if (callback != null) callback.onFailure();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void addSongToPlaylist(String playlistId, ArrayList<String> songsId, Boolean playlistVisibilityIsPublic) {
|
||||
addSongToPlaylist(playlistId, songsId, playlistVisibilityIsPublic, null);
|
||||
}
|
||||
|
||||
public void createPlaylist(String playlistId, String name, ArrayList<String> songsId) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getPlaylistClient()
|
||||
|
|
@ -132,7 +207,7 @@ public class PlaylistRepository {
|
|||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
if (response.isSuccessful()) notifyPlaylistChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -145,20 +220,45 @@ public class PlaylistRepository {
|
|||
public void updatePlaylist(String playlistId, String name, ArrayList<String> songsId) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getPlaylistClient()
|
||||
.deletePlaylist(playlistId)
|
||||
.updatePlaylist(playlistId, name, true, null, null)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
createPlaylist(null, name, songsId);
|
||||
if (response.isSuccessful()) {
|
||||
// After renaming, we need to handle the song list update.
|
||||
// Subsonic doesn't have a "replace all songs" in updatePlaylist.
|
||||
// So we might still need to recreate if the songs changed significantly,
|
||||
// but if we just renamed, we should update the local pinned database.
|
||||
updateLocalPinnedPlaylistName(playlistId, name);
|
||||
notifyPlaylistChanged();
|
||||
}
|
||||
|
||||
// If songsId is provided, we might want to re-sync them.
|
||||
// For now, let's at least fix the name duplication issue.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
private void updateLocalPinnedPlaylistName(String id, String newName) {
|
||||
new Thread(() -> {
|
||||
List<Playlist> pinned = playlistDao.getAllSync();
|
||||
if (pinned != null) {
|
||||
for (Playlist p : pinned) {
|
||||
if (p.getId().equals(id)) {
|
||||
p.setName(newName);
|
||||
playlistDao.insert(p); // Replace strategy will update it
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
public void deletePlaylist(String playlistId) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getPlaylistClient()
|
||||
|
|
@ -166,7 +266,7 @@ public class PlaylistRepository {
|
|||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
if (response.isSuccessful()) notifyPlaylistChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -194,6 +294,49 @@ public class PlaylistRepository {
|
|||
thread.start();
|
||||
}
|
||||
|
||||
@androidx.media3.common.util.UnstableApi
|
||||
public void updatePinnedPlaylists() {
|
||||
updatePinnedPlaylists(null);
|
||||
}
|
||||
|
||||
@androidx.media3.common.util.UnstableApi
|
||||
public void updatePinnedPlaylists(List<String> forceIds) {
|
||||
new Thread(() -> {
|
||||
List<Playlist> pinned = playlistDao.getAllSync();
|
||||
if (pinned != null && !pinned.isEmpty()) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getPlaylistClient()
|
||||
.getPlaylists()
|
||||
.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().getPlaylists() != null) {
|
||||
List<Playlist> remotes = response.body().getSubsonicResponse().getPlaylists().getPlaylists();
|
||||
new Thread(() -> {
|
||||
for (Playlist p : pinned) {
|
||||
for (Playlist r : remotes) {
|
||||
if (p.getId().equals(r.getId())) {
|
||||
p.setName(r.getName());
|
||||
p.setSongCount(r.getSongCount());
|
||||
p.setDuration(r.getDuration());
|
||||
p.setCoverArtId(r.getCoverArtId());
|
||||
playlistDao.insert(p);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
}
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private static class InsertThreadSafe implements Runnable {
|
||||
private final PlaylistDao playlistDao;
|
||||
private final Playlist playlist;
|
||||
|
|
|
|||
|
|
@ -359,6 +359,7 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
|||
private boolean onLongClick() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(Constants.TRACK_OBJECT, songs.get(getBindingAdapterPosition()));
|
||||
bundle.putInt(Constants.ITEM_POSITION, getBindingAdapterPosition());
|
||||
|
||||
click.onMediaLongClick(bundle);
|
||||
|
||||
|
|
|
|||
|
|
@ -216,8 +216,9 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
|
|||
});
|
||||
|
||||
bind.playlistPageShuffleButton.setOnClickListener(v -> {
|
||||
Collections.shuffle(songs);
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
||||
java.util.List<com.cappielloantonio.tempo.subsonic.models.Child> shuffledSongs = new java.util.ArrayList<>(songs);
|
||||
java.util.Collections.shuffle(shuffledSongs);
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, shuffledSongs, 0);
|
||||
activity.setBottomSheetInPeek(true);
|
||||
});
|
||||
}
|
||||
|
|
@ -227,32 +228,33 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
|
|||
private void initBackCover() {
|
||||
playlistPageViewModel.getPlaylistSongLiveList().observe(requireActivity(), songs -> {
|
||||
if (bind != null && songs != null && !songs.isEmpty()) {
|
||||
Collections.shuffle(songs);
|
||||
java.util.List<com.cappielloantonio.tempo.subsonic.models.Child> randomSongs = new java.util.ArrayList<>(songs);
|
||||
java.util.Collections.shuffle(randomSongs);
|
||||
|
||||
// Pic top-left
|
||||
CustomGlideRequest.Builder
|
||||
.from(requireContext(), !songs.isEmpty() ? songs.get(0).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId(), CustomGlideRequest.ResourceType.Song)
|
||||
.from(requireContext(), !randomSongs.isEmpty() ? randomSongs.get(0).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId(), CustomGlideRequest.ResourceType.Song)
|
||||
.build()
|
||||
.transform(new GranularRoundedCorners(CustomGlideRequest.CORNER_RADIUS, 0, 0, 0))
|
||||
.into(bind.playlistCoverImageViewTopLeft);
|
||||
|
||||
// Pic top-right
|
||||
CustomGlideRequest.Builder
|
||||
.from(requireContext(), songs.size() > 1 ? songs.get(1).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId(), CustomGlideRequest.ResourceType.Song)
|
||||
.from(requireContext(), randomSongs.size() > 1 ? randomSongs.get(1).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId(), CustomGlideRequest.ResourceType.Song)
|
||||
.build()
|
||||
.transform(new GranularRoundedCorners(0, CustomGlideRequest.CORNER_RADIUS, 0, 0))
|
||||
.into(bind.playlistCoverImageViewTopRight);
|
||||
|
||||
// Pic bottom-left
|
||||
CustomGlideRequest.Builder
|
||||
.from(requireContext(), songs.size() > 2 ? songs.get(2).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId(), CustomGlideRequest.ResourceType.Song)
|
||||
.from(requireContext(), randomSongs.size() > 2 ? randomSongs.get(2).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId(), CustomGlideRequest.ResourceType.Song)
|
||||
.build()
|
||||
.transform(new GranularRoundedCorners(0, 0, 0, CustomGlideRequest.CORNER_RADIUS))
|
||||
.into(bind.playlistCoverImageViewBottomLeft);
|
||||
|
||||
// Pic bottom-right
|
||||
CustomGlideRequest.Builder
|
||||
.from(requireContext(), songs.size() > 3 ? songs.get(3).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId(), CustomGlideRequest.ResourceType.Song)
|
||||
.from(requireContext(), randomSongs.size() > 3 ? randomSongs.get(3).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId(), CustomGlideRequest.ResourceType.Song)
|
||||
.build()
|
||||
.transform(new GranularRoundedCorners(0, 0, CustomGlideRequest.CORNER_RADIUS, 0))
|
||||
.into(bind.playlistCoverImageViewBottomRight);
|
||||
|
|
@ -271,6 +273,11 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
|
|||
|
||||
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> {
|
||||
songHorizontalAdapter.setItems(songs);
|
||||
if (songs != null) {
|
||||
bind.playlistSongCountLabel.setText(getString(R.string.playlist_song_count, songs.size()));
|
||||
long totalDuration = songs.stream().mapToLong(s -> s.getDuration() != null ? s.getDuration() : 0).sum();
|
||||
bind.playlistDurationLabel.setText(getString(R.string.playlist_duration, MusicUtil.getReadableDurationString(totalDuration, false)));
|
||||
}
|
||||
reapplyPlayback();
|
||||
});
|
||||
}
|
||||
|
|
@ -291,6 +298,7 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
|
|||
|
||||
@Override
|
||||
public void onMediaLongClick(Bundle bundle) {
|
||||
bundle.putString(Constants.PLAYLIST_ID, playlistPageViewModel.getPlaylist().getId());
|
||||
Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -230,6 +230,34 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
|
|||
|
||||
updateDownloadButtons();
|
||||
|
||||
String playlistId = requireArguments().getString(Constants.PLAYLIST_ID);
|
||||
int itemPosition = requireArguments().getInt(Constants.ITEM_POSITION, -1);
|
||||
|
||||
TextView removeFromPlaylist = view.findViewById(R.id.remove_from_playlist_text_view);
|
||||
if (playlistId != null && itemPosition != -1) {
|
||||
removeFromPlaylist.setVisibility(View.VISIBLE);
|
||||
removeFromPlaylist.setOnClickListener(v -> {
|
||||
songBottomSheetViewModel.removeFromPlaylist(playlistId, itemPosition, new com.cappielloantonio.tempo.repository.PlaylistRepository.AddToPlaylistCallback() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
Toast.makeText(requireContext(), R.string.playlist_chooser_dialog_toast_remove_success, Toast.LENGTH_SHORT).show();
|
||||
dismissBottomSheet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure() {
|
||||
Toast.makeText(requireContext(), R.string.playlist_chooser_dialog_toast_remove_failure, Toast.LENGTH_SHORT).show();
|
||||
dismissBottomSheet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAllSkipped() {
|
||||
dismissBottomSheet();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
TextView addToPlaylist = view.findViewById(R.id.add_to_playlist_text_view);
|
||||
addToPlaylist.setOnClickListener(v -> {
|
||||
Bundle bundle = new Bundle();
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ object Constants {
|
|||
const val ARTIST_OBJECT = "ARTIST_OBJECT"
|
||||
const val GENRE_OBJECT = "GENRE_OBJECT"
|
||||
const val PLAYLIST_OBJECT = "PLAYLIST_OBJECT"
|
||||
const val PLAYLIST_ID = "PLAYLIST_ID"
|
||||
const val PODCAST_OBJECT = "PODCAST_OBJECT"
|
||||
const val PODCAST_CHANNEL_OBJECT = "PODCAST_CHANNEL_OBJECT"
|
||||
const val INTERNET_RADIO_STATION_OBJECT = "INTERNET_RADIO_STATION_OBJECT"
|
||||
|
|
|
|||
|
|
@ -20,14 +20,36 @@ public class PlaylistPageViewModel extends AndroidViewModel {
|
|||
private Playlist playlist;
|
||||
private boolean isOffline;
|
||||
|
||||
private final MutableLiveData<List<Child>> songLiveList = new MutableLiveData<>();
|
||||
|
||||
public PlaylistPageViewModel(@NonNull Application application) {
|
||||
super(application);
|
||||
|
||||
playlistRepository = new PlaylistRepository();
|
||||
playlistRepository.getPlaylistUpdateTrigger().observeForever(needsRefresh -> {
|
||||
if (needsRefresh != null && needsRefresh && playlist != null) {
|
||||
refreshSongs();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public LiveData<List<Child>> getPlaylistSongLiveList() {
|
||||
return playlistRepository.getPlaylistSongs(playlist.getId());
|
||||
if (songLiveList.getValue() == null && playlist != null) {
|
||||
refreshSongs();
|
||||
}
|
||||
return songLiveList;
|
||||
}
|
||||
|
||||
private void refreshSongs() {
|
||||
if (playlist == null) return;
|
||||
LiveData<List<Child>> remoteData = playlistRepository.getPlaylistSongs(playlist.getId());
|
||||
remoteData.observeForever(new androidx.lifecycle.Observer<List<Child>>() {
|
||||
@Override
|
||||
public void onChanged(List<Child> songs) {
|
||||
songLiveList.postValue(songs);
|
||||
remoteData.removeObserver(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Playlist getPlaylist() {
|
||||
|
|
@ -35,7 +57,10 @@ public class PlaylistPageViewModel extends AndroidViewModel {
|
|||
}
|
||||
|
||||
public void setPlaylist(Playlist playlist) {
|
||||
this.playlist = playlist;
|
||||
if (this.playlist == null || !this.playlist.getId().equals(playlist.getId())) {
|
||||
this.playlist = playlist;
|
||||
this.songLiveList.setValue(null); // Clear old data immediately
|
||||
}
|
||||
}
|
||||
|
||||
public LiveData<Boolean> isPinned(LifecycleOwner owner) {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import com.cappielloantonio.tempo.model.Download;
|
|||
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
||||
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
||||
import com.cappielloantonio.tempo.repository.FavoriteRepository;
|
||||
import com.cappielloantonio.tempo.repository.PlaylistRepository;
|
||||
import com.cappielloantonio.tempo.repository.SharingRepository;
|
||||
import com.cappielloantonio.tempo.repository.SongRepository;
|
||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||
|
|
@ -39,6 +40,7 @@ public class SongBottomSheetViewModel extends AndroidViewModel {
|
|||
private final ArtistRepository artistRepository;
|
||||
private final FavoriteRepository favoriteRepository;
|
||||
private final SharingRepository sharingRepository;
|
||||
private final PlaylistRepository playlistRepository;
|
||||
|
||||
private Child song;
|
||||
|
||||
|
|
@ -52,6 +54,7 @@ public class SongBottomSheetViewModel extends AndroidViewModel {
|
|||
artistRepository = new ArtistRepository();
|
||||
favoriteRepository = new FavoriteRepository();
|
||||
sharingRepository = new SharingRepository();
|
||||
playlistRepository = new PlaylistRepository();
|
||||
}
|
||||
|
||||
public Child getSong() {
|
||||
|
|
@ -62,6 +65,10 @@ public class SongBottomSheetViewModel extends AndroidViewModel {
|
|||
this.song = song;
|
||||
}
|
||||
|
||||
public void removeFromPlaylist(String playlistId, int index, PlaylistRepository.AddToPlaylistCallback callback) {
|
||||
playlistRepository.removeSongFromPlaylist(playlistId, index, callback);
|
||||
}
|
||||
|
||||
public void setFavorite(Context context) {
|
||||
if (song.getStarred() != null) {
|
||||
if (NetworkUtil.isOffline()) {
|
||||
|
|
|
|||
|
|
@ -164,6 +164,20 @@
|
|||
android:paddingBottom="12dp"
|
||||
android:text="@string/song_bottom_sheet_remove" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/remove_from_playlist_text_view"
|
||||
style="@style/LabelMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingEnd="20dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:text="@string/song_bottom_sheet_remove_from_playlist"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/add_to_playlist_text_view"
|
||||
style="@style/LabelMedium"
|
||||
|
|
|
|||
|
|
@ -228,6 +228,8 @@
|
|||
<string name="playlist_chooser_dialog_title">Aggiungi a una playlist</string>
|
||||
<string name="playlist_chooser_dialog_toast_add_success">Aggiunta di un brano alla playlist</string>
|
||||
<string name="playlist_chooser_dialog_toast_add_failure">Impossibile aggiungere un brano alla playlist</string>
|
||||
<string name="playlist_chooser_dialog_toast_remove_success">Canzone rimossa dalla playlist</string>
|
||||
<string name="playlist_chooser_dialog_toast_remove_failure">Impossibile rimuovere la canzone dalla playlist</string>
|
||||
<string name="playlist_chooser_dialog_toast_all_skipped">Tutte le canzoni sono state saltate perché duplicate</string>
|
||||
<string name="playlist_chooser_dialog_visibility_public">Pubblico</string>
|
||||
<string name="playlist_chooser_dialog_visibility_private">Privato</string>
|
||||
|
|
@ -448,7 +450,8 @@
|
|||
<string name="song_bottom_sheet_instant_mix">Mix istantaneo</string>
|
||||
<string name="song_bottom_sheet_play_next">Riproduci dopo</string>
|
||||
<string name="song_bottom_sheet_rate">Valuta</string>
|
||||
<string name="song_bottom_sheet_remove">Rimuovi</string>
|
||||
<string name="song_bottom_sheet_remove">Rimuovi dal dispositivo</string>
|
||||
<string name="song_bottom_sheet_remove_from_playlist">Rimuovi dalla playlist</string>
|
||||
<string name="song_bottom_sheet_share">Condividi</string>
|
||||
<string name="song_list_page_downloaded">Scaricato</string>
|
||||
<string name="song_list_page_most_played">Tracce più riprodotte</string>
|
||||
|
|
|
|||
|
|
@ -239,6 +239,8 @@
|
|||
<string name="playlist_chooser_dialog_title">Add to a playlist</string>
|
||||
<string name="playlist_chooser_dialog_toast_add_success">Added song(s) to playlist</string>
|
||||
<string name="playlist_chooser_dialog_toast_add_failure">Failed to add song(s) to playlist</string>
|
||||
<string name="playlist_chooser_dialog_toast_remove_success">Removed song from playlist</string>
|
||||
<string name="playlist_chooser_dialog_toast_remove_failure">Failed to remove song from playlist</string>
|
||||
<string name="playlist_chooser_dialog_toast_all_skipped">All songs were skipped as duplicates</string>
|
||||
<string name="playlist_chooser_dialog_visibility_public">Public</string>
|
||||
<string name="playlist_chooser_dialog_visibility_private">Private</string>
|
||||
|
|
@ -472,7 +474,8 @@
|
|||
<string name="song_bottom_sheet_instant_mix">Instant mix</string>
|
||||
<string name="song_bottom_sheet_play_next">Play next</string>
|
||||
<string name="song_bottom_sheet_rate">Rate</string>
|
||||
<string name="song_bottom_sheet_remove">Remove</string>
|
||||
<string name="song_bottom_sheet_remove">Remove from device</string>
|
||||
<string name="song_bottom_sheet_remove_from_playlist">Remove from playlist</string>
|
||||
<string name="song_bottom_sheet_share">Share</string>
|
||||
<string name="song_list_page_downloaded">Downloaded</string>
|
||||
<string name="song_list_page_most_played">Most played tracks</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue