From 4adde9e951cca99fd8750f25511e2964bb09f064 Mon Sep 17 00:00:00 2001 From: CappielloAntonio Date: Thu, 12 Aug 2021 17:33:19 +0200 Subject: [PATCH] implemented the playlist management logic --- .idea/misc.xml | 4 +- .../play/adapter/PlaylistAdapter.java | 15 ++- .../adapter/PlaylistCatalogueAdapter.java | 18 ++- .../PlaylistDialogSongHorizontalAdapter.java | 103 ++++++++++++++++++ .../play/repository/PlaylistRepository.java | 46 +++++++- .../subsonic/api/playlist/PlaylistClient.java | 8 +- .../api/playlist/PlaylistService.java | 5 +- .../fragment/dialog/PlaylistEditorDialog.java | 94 +++++++++++++++- .../viewmodel/PlaylistCatalogueViewModel.java | 5 +- .../viewmodel/PlaylistChooserViewModel.java | 4 +- .../viewmodel/PlaylistEditorViewModel.java | 69 +++++++++++- .../res/layout/dialog_playlist_editor.xml | 11 +- .../item_horizontal_playlist_dialog_track.xml | 93 ++++++++++++++++ 13 files changed, 448 insertions(+), 27 deletions(-) create mode 100644 app/src/main/java/com/cappielloantonio/play/adapter/PlaylistDialogSongHorizontalAdapter.java create mode 100644 app/src/main/res/layout/item_horizontal_playlist_dialog_track.xml diff --git a/.idea/misc.xml b/.idea/misc.xml index 15571fbc..708be2f4 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -22,10 +22,11 @@ + - + @@ -34,6 +35,7 @@ + diff --git a/app/src/main/java/com/cappielloantonio/play/adapter/PlaylistAdapter.java b/app/src/main/java/com/cappielloantonio/play/adapter/PlaylistAdapter.java index e3bcff1c..40b8ef1f 100644 --- a/app/src/main/java/com/cappielloantonio/play/adapter/PlaylistAdapter.java +++ b/app/src/main/java/com/cappielloantonio/play/adapter/PlaylistAdapter.java @@ -17,6 +17,7 @@ import com.cappielloantonio.play.R; import com.cappielloantonio.play.glide.CustomGlideRequest; import com.cappielloantonio.play.model.Playlist; import com.cappielloantonio.play.ui.activity.MainActivity; +import com.cappielloantonio.play.ui.fragment.dialog.PlaylistEditorDialog; import com.cappielloantonio.play.util.MusicUtil; import java.util.ArrayList; @@ -70,7 +71,7 @@ public class PlaylistAdapter extends RecyclerView.Adapter { + private static final String TAG = "PlaylistDialogSongHorizontalAdapter"; + + private List songs; + private LayoutInflater mInflater; + private MainActivity mainActivity; + private Context context; + private FragmentManager fragmentManager; + + public PlaylistDialogSongHorizontalAdapter(MainActivity mainActivity, Context context, FragmentManager fragmentManager) { + this.mainActivity = mainActivity; + this.context = context; + this.fragmentManager = fragmentManager; + this.mInflater = LayoutInflater.from(context); + this.songs = new ArrayList<>(); + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = mInflater.inflate(R.layout.item_horizontal_playlist_dialog_track, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(ViewHolder holder, int position) { + Song song = songs.get(position); + + holder.songTitle.setText(MusicUtil.getReadableString(song.getTitle())); + holder.songArtist.setText(MusicUtil.getReadableString(song.getArtistName())); + holder.songDuration.setText(MusicUtil.getReadableDurationString(song.getDuration(), false)); + + CustomGlideRequest.Builder + .from(context, song.getPrimary(), CustomGlideRequest.SONG_PIC, null) + .build() + .transform(new RoundedCorners(CustomGlideRequest.CORNER_RADIUS)) + .into(holder.cover); + } + + @Override + public int getItemCount() { + return songs.size(); + } + + public List getItems() { + return this.songs; + } + + public void setItems(List songs) { + this.songs = songs; + notifyDataSetChanged(); + } + + public Song getItem(int id) { + return songs.get(id); + } + + public class ViewHolder extends RecyclerView.ViewHolder { + TextView songTitle; + TextView songArtist; + TextView songDuration; + ImageView cover; + + ViewHolder(View itemView) { + super(itemView); + + songTitle = itemView.findViewById(R.id.playlist_dialog_song_title_text_view); + songArtist = itemView.findViewById(R.id.playlist_dialog_album_artist_text_view); + songDuration = itemView.findViewById(R.id.playlist_dialog_song_duration_text_view); + cover = itemView.findViewById(R.id.playlist_dialog_song_cover_image_view); + + songTitle.setSelected(true); + } + } +} diff --git a/app/src/main/java/com/cappielloantonio/play/repository/PlaylistRepository.java b/app/src/main/java/com/cappielloantonio/play/repository/PlaylistRepository.java index 15c41c55..789bb4fb 100644 --- a/app/src/main/java/com/cappielloantonio/play/repository/PlaylistRepository.java +++ b/app/src/main/java/com/cappielloantonio/play/repository/PlaylistRepository.java @@ -78,10 +78,10 @@ public class PlaylistRepository { return listLivePlaylistSongs; } - public void addSongToPlaylist(String playlistId, String songId) { + public void addSongToPlaylist(String playlistId, ArrayList songsId) { App.getSubsonicClientInstance(application, false) .getPlaylistClient() - .updatePlaylist(playlistId, null, true, songId, null) + .updatePlaylist(playlistId, null, true, songsId, null) .enqueue(new Callback() { @Override public void onResponse(Call call, Response response) { @@ -97,10 +97,48 @@ public class PlaylistRepository { }); } - public void createPlaylist(String name, String songId) { + public void createPlaylist(String playlistId, String name, ArrayList songsId) { App.getSubsonicClientInstance(application, false) .getPlaylistClient() - .createPlaylist(null, name, songId) + .createPlaylist(playlistId, name, songsId) + .enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.body().getStatus().getValue().equals(ResponseStatus.OK)) { + + } + } + + @Override + public void onFailure(Call call, Throwable t) { + + } + }); + } + + public void updatePlaylist(String playlistId, String name, boolean isPublic, ArrayList songIdToAdd, ArrayList songIndexToRemove) { + App.getSubsonicClientInstance(application, false) + .getPlaylistClient() + .updatePlaylist(playlistId, name, isPublic, songIdToAdd, songIndexToRemove) + .enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.body().getStatus().getValue().equals(ResponseStatus.OK)) { + + } + } + + @Override + public void onFailure(Call call, Throwable t) { + + } + }); + } + + public void deletePlaylist(String playlistId) { + App.getSubsonicClientInstance(application, false) + .getPlaylistClient() + .deletePlaylist(playlistId) .enqueue(new Callback() { @Override public void onResponse(Call call, Response response) { diff --git a/app/src/main/java/com/cappielloantonio/play/subsonic/api/playlist/PlaylistClient.java b/app/src/main/java/com/cappielloantonio/play/subsonic/api/playlist/PlaylistClient.java index e879d662..ea33b535 100644 --- a/app/src/main/java/com/cappielloantonio/play/subsonic/api/playlist/PlaylistClient.java +++ b/app/src/main/java/com/cappielloantonio/play/subsonic/api/playlist/PlaylistClient.java @@ -6,6 +6,8 @@ import com.cappielloantonio.play.subsonic.Subsonic; import com.cappielloantonio.play.subsonic.models.SubsonicResponse; import com.tickaroo.tikxml.retrofit.TikXmlConverterFactory; +import java.util.ArrayList; + import okhttp3.OkHttpClient; import okhttp3.logging.HttpLoggingInterceptor; import retrofit2.Call; @@ -40,12 +42,12 @@ public class PlaylistClient { return playlistService.getPlaylist(subsonic.getParams(), id); } - public Call createPlaylist(String playlistId, String name, String songId) { + public Call createPlaylist(String playlistId, String name, ArrayList songsId) { Log.d(TAG, "createPlaylist()"); - return playlistService.createPlaylist(subsonic.getParams(), playlistId, name, songId); + return playlistService.createPlaylist(subsonic.getParams(), playlistId, name, songsId); } - public Call updatePlaylist(String playlistId, String name, boolean isPublic, String songIdToAdd, String songIndexToRemove) { + public Call updatePlaylist(String playlistId, String name, boolean isPublic, ArrayList songIdToAdd, ArrayList songIndexToRemove) { Log.d(TAG, "updatePlaylist()"); return playlistService.updatePlaylist(subsonic.getParams(), playlistId, name, isPublic, songIdToAdd, songIndexToRemove); } diff --git a/app/src/main/java/com/cappielloantonio/play/subsonic/api/playlist/PlaylistService.java b/app/src/main/java/com/cappielloantonio/play/subsonic/api/playlist/PlaylistService.java index 5f27e908..e90846ae 100644 --- a/app/src/main/java/com/cappielloantonio/play/subsonic/api/playlist/PlaylistService.java +++ b/app/src/main/java/com/cappielloantonio/play/subsonic/api/playlist/PlaylistService.java @@ -2,6 +2,7 @@ package com.cappielloantonio.play.subsonic.api.playlist; import com.cappielloantonio.play.subsonic.models.SubsonicResponse; +import java.util.ArrayList; import java.util.Map; import retrofit2.Call; @@ -17,10 +18,10 @@ public interface PlaylistService { Call getPlaylist(@QueryMap Map params, @Query("id") String id); @GET("createPlaylist") - Call createPlaylist(@QueryMap Map params, @Query("playlistId") String playlistId, @Query("name") String name, @Query("songId") String songId); + Call createPlaylist(@QueryMap Map params, @Query("playlistId") String playlistId, @Query("name") String name, @Query("songId") ArrayList songsId); @GET("updatePlaylist") - Call updatePlaylist(@QueryMap Map params, @Query("playlistId") String playlistId, @Query("name") String name, @Query("public") boolean isPublic, @Query("songIdToAdd") String songIdToAdd, @Query("songIndexToRemove") String songIndexToRemove); + Call updatePlaylist(@QueryMap Map params, @Query("playlistId") String playlistId, @Query("name") String name, @Query("public") boolean isPublic, @Query("songIdToAdd") ArrayList songIdToAdd, @Query("songIndexToRemove") ArrayList songIndexToRemove); @GET("deletePlaylist") Call deletePlaylist(@QueryMap Map params, @Query("id") String id); diff --git a/app/src/main/java/com/cappielloantonio/play/ui/fragment/dialog/PlaylistEditorDialog.java b/app/src/main/java/com/cappielloantonio/play/ui/fragment/dialog/PlaylistEditorDialog.java index 6c8a776d..709107c6 100644 --- a/app/src/main/java/com/cappielloantonio/play/ui/fragment/dialog/PlaylistEditorDialog.java +++ b/app/src/main/java/com/cappielloantonio/play/ui/fragment/dialog/PlaylistEditorDialog.java @@ -7,14 +7,21 @@ import android.os.Bundle; import android.text.TextUtils; import android.view.LayoutInflater; +import androidx.annotation.NonNull; import androidx.fragment.app.DialogFragment; import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import com.cappielloantonio.play.R; +import com.cappielloantonio.play.adapter.PlaylistDialogSongHorizontalAdapter; import com.cappielloantonio.play.databinding.DialogPlaylistEditorBinding; import com.cappielloantonio.play.ui.activity.MainActivity; +import com.cappielloantonio.play.util.MusicUtil; import com.cappielloantonio.play.viewmodel.PlaylistEditorViewModel; +import java.util.Collections; import java.util.Objects; public class PlaylistEditorDialog extends DialogFragment { @@ -26,6 +33,7 @@ public class PlaylistEditorDialog extends DialogFragment { private PlaylistEditorViewModel playlistEditorViewModel; private String playlistName; + private PlaylistDialogSongHorizontalAdapter playlistDialogSongHorizontalAdapter; @Override public Dialog onCreateDialog(Bundle savedInstanceState) { @@ -41,6 +49,7 @@ public class PlaylistEditorDialog extends DialogFragment { .setTitle("Create playlist") .setPositiveButton("Save", (dialog, id) -> { }) + .setNeutralButton("Delete", (dialog, id) -> dialog.cancel()) .setNegativeButton("Cancel", (dialog, id) -> dialog.cancel()); return builder.create(); @@ -50,15 +59,28 @@ public class PlaylistEditorDialog extends DialogFragment { public void onStart() { super.onStart(); - setSongInfo(); + setParameterInfo(); setButtonAction(); + initSongsView(); } - private void setSongInfo() { + private void setParameterInfo() { if (getArguments() != null) { - playlistEditorViewModel.setSongToAdd(getArguments().getParcelable("song_object")); + if (getArguments().getParcelable("song_object") != null) { + playlistEditorViewModel.setSongToAdd(getArguments().getParcelable("song_object")); + playlistEditorViewModel.setPlaylistToEdit(null); + } + else if (getArguments().getParcelable("playlist_object") != null) { + playlistEditorViewModel.setSongToAdd(null); + playlistEditorViewModel.setPlaylistToEdit(getArguments().getParcelable("playlist_object")); + + if (playlistEditorViewModel.getPlaylistToEdit() != null) { + bind.playlistNameTextView.setText(MusicUtil.getReadableString(playlistEditorViewModel.getPlaylistToEdit().getName())); + } + } } else { playlistEditorViewModel.setSongToAdd(null); + playlistEditorViewModel.setPlaylistToEdit(null); } } @@ -69,10 +91,74 @@ public class PlaylistEditorDialog extends DialogFragment { ((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> { if (validateInput()) { - playlistEditorViewModel.createPlaylist(playlistName); + if (playlistEditorViewModel.getSongToAdd() != null) { + playlistEditorViewModel.createPlaylist(playlistName); + } else if (playlistEditorViewModel.getPlaylistToEdit() != null) { + playlistEditorViewModel.updatePlaylist(playlistName); + } + Objects.requireNonNull(getDialog()).dismiss(); } }); + + ((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> { + playlistEditorViewModel.deletePlaylist(); + Objects.requireNonNull(getDialog()).dismiss(); + }); + } + + private void initSongsView() { + bind.playlistSongRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); + bind.playlistSongRecyclerView.setHasFixedSize(true); + + playlistDialogSongHorizontalAdapter = new PlaylistDialogSongHorizontalAdapter(activity, requireContext(), getChildFragmentManager()); + bind.playlistSongRecyclerView.setAdapter(playlistDialogSongHorizontalAdapter); + + playlistEditorViewModel.getPlaylistSongLiveList().observe(requireActivity(), songs -> { + playlistDialogSongHorizontalAdapter.setItems(songs); + }); + + new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT) { + int originalPosition = -1; + int fromPosition = -1; + int toPosition = -1; + + @Override + public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { + if (originalPosition == -1) + originalPosition = viewHolder.getBindingAdapterPosition(); + + fromPosition = viewHolder.getBindingAdapterPosition(); + toPosition = target.getBindingAdapterPosition(); + + Collections.swap(playlistDialogSongHorizontalAdapter.getItems(), fromPosition, toPosition); + recyclerView.getAdapter().notifyItemMoved(fromPosition, toPosition); + + return false; + } + + @Override + public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { + super.clearView(recyclerView, viewHolder); + + /* + * Qui vado a riscivere tutta la table Queue, quando teoricamente potrei solo swappare l'ordine degli elementi interessati + * Nel caso la coda contenesse parecchi brani, potrebbero verificarsi rallentamenti pesanti + */ + playlistEditorViewModel.orderPlaylistSongLiveListAfterSwap(playlistDialogSongHorizontalAdapter.getItems()); + + originalPosition = -1; + fromPosition = -1; + toPosition = -1; + } + + @Override + public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { + playlistEditorViewModel.removeFromPlaylistSongLiveList(playlistDialogSongHorizontalAdapter.getItem(viewHolder.getBindingAdapterPosition())); + bind.playlistSongRecyclerView.getAdapter().notifyItemRemoved(viewHolder.getBindingAdapterPosition()); + } + } + ).attachToRecyclerView(bind.playlistSongRecyclerView); } private boolean validateInput() { diff --git a/app/src/main/java/com/cappielloantonio/play/viewmodel/PlaylistCatalogueViewModel.java b/app/src/main/java/com/cappielloantonio/play/viewmodel/PlaylistCatalogueViewModel.java index a8583853..2d39d5ba 100644 --- a/app/src/main/java/com/cappielloantonio/play/viewmodel/PlaylistCatalogueViewModel.java +++ b/app/src/main/java/com/cappielloantonio/play/viewmodel/PlaylistCatalogueViewModel.java @@ -31,12 +31,9 @@ public class PlaylistCatalogueViewModel extends AndroidViewModel { super(application); playlistRepository = new PlaylistRepository(application); - - playlists = playlistRepository.getPlaylists(false, -1); } public LiveData> getPlaylistList() { - return playlists; + return playlistRepository.getPlaylists(false, -1); } - } diff --git a/app/src/main/java/com/cappielloantonio/play/viewmodel/PlaylistChooserViewModel.java b/app/src/main/java/com/cappielloantonio/play/viewmodel/PlaylistChooserViewModel.java index 6372d400..eaeb3574 100644 --- a/app/src/main/java/com/cappielloantonio/play/viewmodel/PlaylistChooserViewModel.java +++ b/app/src/main/java/com/cappielloantonio/play/viewmodel/PlaylistChooserViewModel.java @@ -11,6 +11,8 @@ import com.cappielloantonio.play.model.Playlist; import com.cappielloantonio.play.model.Song; import com.cappielloantonio.play.repository.PlaylistRepository; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; public class PlaylistChooserViewModel extends AndroidViewModel { @@ -32,7 +34,7 @@ public class PlaylistChooserViewModel extends AndroidViewModel { } public void addSongToPlaylist(String playlistId) { - playlistRepository.addSongToPlaylist(playlistId, toAdd.getId()); + playlistRepository.addSongToPlaylist(playlistId, new ArrayList(Collections.singletonList(toAdd.getId()))); } public void setSongToAdd(Song song) { diff --git a/app/src/main/java/com/cappielloantonio/play/viewmodel/PlaylistEditorViewModel.java b/app/src/main/java/com/cappielloantonio/play/viewmodel/PlaylistEditorViewModel.java index 5a8f8314..6dd2bad7 100644 --- a/app/src/main/java/com/cappielloantonio/play/viewmodel/PlaylistEditorViewModel.java +++ b/app/src/main/java/com/cappielloantonio/play/viewmodel/PlaylistEditorViewModel.java @@ -1,38 +1,97 @@ package com.cappielloantonio.play.viewmodel; import android.app.Application; +import android.util.Log; import androidx.annotation.NonNull; import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; -import com.cappielloantonio.play.model.Server; +import com.cappielloantonio.play.model.Playlist; import com.cappielloantonio.play.model.Song; import com.cappielloantonio.play.repository.PlaylistRepository; -import com.cappielloantonio.play.repository.ServerRepository; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Objects; public class PlaylistEditorViewModel extends AndroidViewModel { + private static final String TAG = "PlaylistEditorViewModel"; + private PlaylistRepository playlistRepository; private Song toAdd; + private Playlist toEdit; + + private MutableLiveData> songLiveList = new MutableLiveData<>(); public PlaylistEditorViewModel(@NonNull Application application) { super(application); playlistRepository = new PlaylistRepository(application); + + Log.d(TAG, "PlaylistEditorViewModel()"); } public void createPlaylist(String name) { - playlistRepository.createPlaylist(name, toAdd.getId()); + playlistRepository.createPlaylist(null, name, new ArrayList(Collections.singletonList(toAdd.getId()))); } - public void updatePlaylist(String playlistId) { + public void updatePlaylist(String name) { + playlistRepository.deletePlaylist(toEdit.getId()); + playlistRepository.createPlaylist(toEdit.getId(), name, getPlaylistSongIds()); + } + public void deletePlaylist() { + if (toEdit != null) playlistRepository.deletePlaylist(toEdit.getId()); + } + + public Song getSongToAdd() { + return toAdd; } public void setSongToAdd(Song song) { - toAdd = song; + this.toAdd = song; + } + + public Playlist getPlaylistToEdit() { + return toEdit; + } + + public void setPlaylistToEdit(Playlist playlist) { + this.toEdit = playlist; + + if (playlist != null) { + this.songLiveList = playlistRepository.getPlaylistSongs(toEdit.getId()); + } else { + this.songLiveList = new MutableLiveData<>(); + } + } + + public LiveData> getPlaylistSongLiveList() { + return songLiveList; + } + + public void removeFromPlaylistSongLiveList(Song song) { + List songs = songLiveList.getValue(); + Objects.requireNonNull(songs).remove(song); + songLiveList.postValue(songs); + } + + public void orderPlaylistSongLiveListAfterSwap(List songs) { + songLiveList.postValue(songs); + } + + private ArrayList getPlaylistSongIds() { + List songs = songLiveList.getValue(); + ArrayList ids = new ArrayList<>(); + + for (Song song : songs) { + ids.add(song.getId()); + } + + return ids; } } diff --git a/app/src/main/res/layout/dialog_playlist_editor.xml b/app/src/main/res/layout/dialog_playlist_editor.xml index 14257d53..fda7d389 100644 --- a/app/src/main/res/layout/dialog_playlist_editor.xml +++ b/app/src/main/res/layout/dialog_playlist_editor.xml @@ -25,6 +25,15 @@ android:hint="Playlist Name" android:inputType="textNoSuggestions" android:textCursorDrawable="@null" /> - + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_horizontal_playlist_dialog_track.xml b/app/src/main/res/layout/item_horizontal_playlist_dialog_track.xml new file mode 100644 index 00000000..3e6b6384 --- /dev/null +++ b/app/src/main/res/layout/item_horizontal_playlist_dialog_track.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file