diff --git a/.idea/misc.xml b/.idea/misc.xml
index 0de3292c..15571fbc 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -20,10 +20,12 @@
+
+
-
+
@@ -31,11 +33,13 @@
+
+
diff --git a/app/src/main/java/com/cappielloantonio/play/adapter/PlaylistHorizontalAdapter.java b/app/src/main/java/com/cappielloantonio/play/adapter/PlaylistHorizontalAdapter.java
new file mode 100644
index 00000000..6cf033ce
--- /dev/null
+++ b/app/src/main/java/com/cappielloantonio/play/adapter/PlaylistHorizontalAdapter.java
@@ -0,0 +1,91 @@
+package com.cappielloantonio.play.adapter;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.cappielloantonio.play.R;
+import com.cappielloantonio.play.model.Playlist;
+import com.cappielloantonio.play.ui.fragment.dialog.PlaylistChooserDialog;
+import com.cappielloantonio.play.util.MusicUtil;
+import com.cappielloantonio.play.viewmodel.PlaylistChooserViewModel;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PlaylistHorizontalAdapter extends RecyclerView.Adapter {
+ private static final String TAG = "PlaylistHorizontalAdapter";
+
+ private List playlists;
+ private LayoutInflater mInflater;
+ private Context context;
+
+ private PlaylistChooserViewModel playlistChooserViewModel;
+ private PlaylistChooserDialog playlistChooserDialog;
+
+ public PlaylistHorizontalAdapter(Context context, PlaylistChooserViewModel playlistChooserViewModel, PlaylistChooserDialog playlistChooserDialog) {
+ this.context = context;
+ this.mInflater = LayoutInflater.from(context);
+ this.playlists = new ArrayList<>();
+
+ this.playlistChooserViewModel = playlistChooserViewModel;
+ this.playlistChooserDialog = playlistChooserDialog;
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = mInflater.inflate(R.layout.item_horizontal_playlist, parent, false);
+ return new ViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder holder, int position) {
+ Playlist playlist = playlists.get(position);
+
+ holder.playlistTitle.setText(MusicUtil.getReadableString(playlist.getName()));
+ holder.playlistTrackCount.setText(MusicUtil.getReadableString(playlist.getSongCount() + " tracks"));
+ holder.playlistDuration.setText(MusicUtil.getReadableDurationString(playlist.getDuration(), false));
+ }
+
+ @Override
+ public int getItemCount() {
+ return playlists.size();
+ }
+
+ public void setItems(List playlists) {
+ this.playlists = playlists;
+ notifyDataSetChanged();
+ }
+
+ public Playlist getItem(int id) {
+ return playlists.get(id);
+ }
+
+ public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
+ TextView playlistTitle;
+ TextView playlistTrackCount;
+ TextView playlistDuration;
+
+ ViewHolder(View itemView) {
+ super(itemView);
+
+ playlistTitle = itemView.findViewById(R.id.playlist_dialog_title_text_view);
+ playlistTrackCount = itemView.findViewById(R.id.playlist_dialog_count_text_view);
+ playlistDuration = itemView.findViewById(R.id.playlist_dialog_duration_text_view);
+
+ itemView.setOnClickListener(this);
+
+ playlistTitle.setSelected(true);
+ }
+
+ @Override
+ public void onClick(View view) {
+ playlistChooserViewModel.addSongToPlaylist(playlists.get(getBindingAdapterPosition()).getId());
+ playlistChooserDialog.dismiss();
+ }
+ }
+}
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 7857f979..15c41c55 100644
--- a/app/src/main/java/com/cappielloantonio/play/repository/PlaylistRepository.java
+++ b/app/src/main/java/com/cappielloantonio/play/repository/PlaylistRepository.java
@@ -77,4 +77,42 @@ public class PlaylistRepository {
return listLivePlaylistSongs;
}
+
+ public void addSongToPlaylist(String playlistId, String songId) {
+ App.getSubsonicClientInstance(application, false)
+ .getPlaylistClient()
+ .updatePlaylist(playlistId, null, true, songId, null)
+ .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 createPlaylist(String name, String songId) {
+ App.getSubsonicClientInstance(application, false)
+ .getPlaylistClient()
+ .createPlaylist(null, name, songId)
+ .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) {
+
+ }
+ });
+ }
}
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 26f6df15..e879d662 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
@@ -40,6 +40,21 @@ public class PlaylistClient {
return playlistService.getPlaylist(subsonic.getParams(), id);
}
+ public Call createPlaylist(String playlistId, String name, String songId) {
+ Log.d(TAG, "createPlaylist()");
+ return playlistService.createPlaylist(subsonic.getParams(), playlistId, name, songId);
+ }
+
+ public Call updatePlaylist(String playlistId, String name, boolean isPublic, String songIdToAdd, String songIndexToRemove) {
+ Log.d(TAG, "updatePlaylist()");
+ return playlistService.updatePlaylist(subsonic.getParams(), playlistId, name, isPublic, songIdToAdd, songIndexToRemove);
+ }
+
+ public Call deletePlaylist(String id) {
+ Log.d(TAG, "deletePlaylist()");
+ return playlistService.deletePlaylist(subsonic.getParams(), id);
+ }
+
private OkHttpClient getOkHttpClient() {
return new OkHttpClient.Builder()
.addInterceptor(getHttpLoggingInterceptor())
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 43f8e85d..5f27e908 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
@@ -15,4 +15,13 @@ public interface PlaylistService {
@GET("getPlaylist")
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);
+
+ @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);
+
+ @GET("deletePlaylist")
+ Call deletePlaylist(@QueryMap Map params, @Query("id") String id);
}
diff --git a/app/src/main/java/com/cappielloantonio/play/ui/fragment/bottomsheetdialog/SongBottomSheetDialog.java b/app/src/main/java/com/cappielloantonio/play/ui/fragment/bottomsheetdialog/SongBottomSheetDialog.java
index f4d307f7..9248b681 100644
--- a/app/src/main/java/com/cappielloantonio/play/ui/fragment/bottomsheetdialog/SongBottomSheetDialog.java
+++ b/app/src/main/java/com/cappielloantonio/play/ui/fragment/bottomsheetdialog/SongBottomSheetDialog.java
@@ -25,6 +25,8 @@ import com.cappielloantonio.play.repository.QueueRepository;
import com.cappielloantonio.play.repository.SongRepository;
import com.cappielloantonio.play.service.MusicPlayerRemote;
import com.cappielloantonio.play.ui.activity.MainActivity;
+import com.cappielloantonio.play.ui.fragment.dialog.PlaylistChooserDialog;
+import com.cappielloantonio.play.ui.fragment.dialog.ServerSignupDialog;
import com.cappielloantonio.play.util.DownloadUtil;
import com.cappielloantonio.play.util.MusicUtil;
import com.cappielloantonio.play.viewmodel.SongBottomSheetViewModel;
@@ -49,6 +51,7 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
private TextView playNext;
private TextView addToQueue;
private TextView download;
+ private TextView addToPlaylist;
private TextView goToAlbum;
private TextView goToArtist;
@@ -139,6 +142,18 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
dismissBottomSheet();
});
+ addToPlaylist = view.findViewById(R.id.add_to_playlist_text_view);
+ addToPlaylist.setOnClickListener(v -> {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable("song_object", song);
+
+ PlaylistChooserDialog dialog = new PlaylistChooserDialog();
+ dialog.setArguments(bundle);
+ dialog.show(requireActivity().getSupportFragmentManager(), null);
+
+ dismissBottomSheet();
+ });
+
goToAlbum = view.findViewById(R.id.go_to_album_text_view);
goToAlbum.setOnClickListener(v -> {
songBottomSheetViewModel.getAlbum().observe(requireActivity(), album -> {
diff --git a/app/src/main/java/com/cappielloantonio/play/ui/fragment/dialog/PlaylistChooserDialog.java b/app/src/main/java/com/cappielloantonio/play/ui/fragment/dialog/PlaylistChooserDialog.java
new file mode 100644
index 00000000..65fd1ee7
--- /dev/null
+++ b/app/src/main/java/com/cappielloantonio/play/ui/fragment/dialog/PlaylistChooserDialog.java
@@ -0,0 +1,103 @@
+package com.cappielloantonio.play.ui.fragment.dialog;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import androidx.fragment.app.DialogFragment;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.recyclerview.widget.LinearLayoutManager;
+
+import com.cappielloantonio.play.R;
+import com.cappielloantonio.play.adapter.PlaylistHorizontalAdapter;
+import com.cappielloantonio.play.databinding.DialogPlaylistChooserBinding;
+import com.cappielloantonio.play.ui.activity.MainActivity;
+import com.cappielloantonio.play.viewmodel.PlaylistChooserViewModel;
+
+import java.util.Objects;
+
+public class PlaylistChooserDialog extends DialogFragment {
+ private static final String TAG = "ServerSignupDialog";
+
+ private DialogPlaylistChooserBinding bind;
+ private MainActivity activity;
+ private Context context;
+ private PlaylistChooserViewModel playlistChooserViewModel;
+
+ private PlaylistHorizontalAdapter playlistHorizontalAdapter;
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ activity = (MainActivity) getActivity();
+ context = requireContext();
+
+ bind = DialogPlaylistChooserBinding.inflate(LayoutInflater.from(requireContext()));
+ playlistChooserViewModel = new ViewModelProvider(requireActivity()).get(PlaylistChooserViewModel.class);
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.AppTheme_AlertDialog);
+
+ builder.setView(bind.getRoot())
+ .setTitle("Add to a playlist")
+ .setNeutralButton("Create", (dialog, id) -> {
+ })
+ .setNegativeButton("Cancel", (dialog, id) -> dialog.cancel());
+
+ return builder.create();
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ initPlaylistView();
+ setSongInfo();
+ setButtonAction();
+ }
+
+ private void setSongInfo() {
+ if (getArguments() != null) {
+ playlistChooserViewModel.setSongToAdd(getArguments().getParcelable("song_object"));
+ } else {
+ playlistChooserViewModel.setSongToAdd(null);
+ }
+ }
+
+ private void setButtonAction() {
+ ((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setTextColor(getResources().getColor(R.color.colorAccent, null));
+ ((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(getResources().getColor(R.color.colorAccent, null));
+
+ ((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable("song_object", playlistChooserViewModel.getSongToAdd());
+
+ PlaylistEditorDialog dialog = new PlaylistEditorDialog();
+ dialog.setArguments(bundle);
+ dialog.show(requireActivity().getSupportFragmentManager(), null);
+
+ Objects.requireNonNull(getDialog()).dismiss();
+ });
+ }
+
+ private void initPlaylistView() {
+ bind.playlistDialogRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
+ bind.playlistDialogRecyclerView.setHasFixedSize(true);
+
+ playlistHorizontalAdapter = new PlaylistHorizontalAdapter(requireContext(), playlistChooserViewModel, this);
+ bind.playlistDialogRecyclerView.setAdapter(playlistHorizontalAdapter);
+
+ playlistChooserViewModel.getPlaylistList().observe(requireActivity(), playlists -> {
+ if(playlists.size() > 0) {
+ if (bind != null) bind.noPlaylistsCreatedTextView.setVisibility(View.GONE);
+ if (bind != null) bind.playlistDialogRecyclerView.setVisibility(View.VISIBLE);
+ playlistHorizontalAdapter.setItems(playlists);
+ }
+ else {
+ if (bind != null) bind.noPlaylistsCreatedTextView.setVisibility(View.VISIBLE);
+ if (bind != null) bind.playlistDialogRecyclerView.setVisibility(View.GONE);
+ }
+ });
+ }
+}
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
new file mode 100644
index 00000000..6c8a776d
--- /dev/null
+++ b/app/src/main/java/com/cappielloantonio/play/ui/fragment/dialog/PlaylistEditorDialog.java
@@ -0,0 +1,88 @@
+package com.cappielloantonio.play.ui.fragment.dialog;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+
+import androidx.fragment.app.DialogFragment;
+import androidx.lifecycle.ViewModelProvider;
+
+import com.cappielloantonio.play.R;
+import com.cappielloantonio.play.databinding.DialogPlaylistEditorBinding;
+import com.cappielloantonio.play.ui.activity.MainActivity;
+import com.cappielloantonio.play.viewmodel.PlaylistEditorViewModel;
+
+import java.util.Objects;
+
+public class PlaylistEditorDialog extends DialogFragment {
+ private static final String TAG = "ServerSignupDialog";
+
+ private DialogPlaylistEditorBinding bind;
+ private MainActivity activity;
+ private Context context;
+ private PlaylistEditorViewModel playlistEditorViewModel;
+
+ private String playlistName;
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ activity = (MainActivity) getActivity();
+ context = requireContext();
+
+ bind = DialogPlaylistEditorBinding.inflate(LayoutInflater.from(requireContext()));
+ playlistEditorViewModel = new ViewModelProvider(requireActivity()).get(PlaylistEditorViewModel.class);
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.AppTheme_AlertDialog);
+
+ builder.setView(bind.getRoot())
+ .setTitle("Create playlist")
+ .setPositiveButton("Save", (dialog, id) -> {
+ })
+ .setNegativeButton("Cancel", (dialog, id) -> dialog.cancel());
+
+ return builder.create();
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ setSongInfo();
+ setButtonAction();
+ }
+
+ private void setSongInfo() {
+ if (getArguments() != null) {
+ playlistEditorViewModel.setSongToAdd(getArguments().getParcelable("song_object"));
+ } else {
+ playlistEditorViewModel.setSongToAdd(null);
+ }
+ }
+
+ private void setButtonAction() {
+ ((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setTextColor(getResources().getColor(R.color.colorAccent, null));
+ ((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(getResources().getColor(R.color.colorAccent, null));
+ ((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(getResources().getColor(R.color.colorAccent, null));
+
+ ((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
+ if (validateInput()) {
+ playlistEditorViewModel.createPlaylist(playlistName);
+ Objects.requireNonNull(getDialog()).dismiss();
+ }
+ });
+ }
+
+ private boolean validateInput() {
+ playlistName = bind.playlistNameTextView.getText().toString().trim();
+
+ if (TextUtils.isEmpty(playlistName)) {
+ bind.playlistNameTextView.setError("Required");
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/app/src/main/java/com/cappielloantonio/play/viewmodel/PlaylistChooserViewModel.java b/app/src/main/java/com/cappielloantonio/play/viewmodel/PlaylistChooserViewModel.java
new file mode 100644
index 00000000..6372d400
--- /dev/null
+++ b/app/src/main/java/com/cappielloantonio/play/viewmodel/PlaylistChooserViewModel.java
@@ -0,0 +1,45 @@
+package com.cappielloantonio.play.viewmodel;
+
+import android.app.Application;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+
+import com.cappielloantonio.play.model.Playlist;
+import com.cappielloantonio.play.model.Song;
+import com.cappielloantonio.play.repository.PlaylistRepository;
+
+import java.util.List;
+
+public class PlaylistChooserViewModel extends AndroidViewModel {
+ private PlaylistRepository playlistRepository;
+
+ private MutableLiveData> playlists;
+ private Song toAdd;
+
+ public PlaylistChooserViewModel(@NonNull Application application) {
+ super(application);
+
+ playlistRepository = new PlaylistRepository(application);
+
+ playlists = playlistRepository.getPlaylists(false, -1);
+ }
+
+ public LiveData> getPlaylistList() {
+ return playlists;
+ }
+
+ public void addSongToPlaylist(String playlistId) {
+ playlistRepository.addSongToPlaylist(playlistId, toAdd.getId());
+ }
+
+ public void setSongToAdd(Song song) {
+ toAdd = song;
+ }
+
+ public Song getSongToAdd() {
+ return toAdd;
+ }
+}
diff --git a/app/src/main/java/com/cappielloantonio/play/viewmodel/PlaylistEditorViewModel.java b/app/src/main/java/com/cappielloantonio/play/viewmodel/PlaylistEditorViewModel.java
new file mode 100644
index 00000000..5a8f8314
--- /dev/null
+++ b/app/src/main/java/com/cappielloantonio/play/viewmodel/PlaylistEditorViewModel.java
@@ -0,0 +1,38 @@
+package com.cappielloantonio.play.viewmodel;
+
+import android.app.Application;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.LiveData;
+
+import com.cappielloantonio.play.model.Server;
+import com.cappielloantonio.play.model.Song;
+import com.cappielloantonio.play.repository.PlaylistRepository;
+import com.cappielloantonio.play.repository.ServerRepository;
+
+import java.util.List;
+
+public class PlaylistEditorViewModel extends AndroidViewModel {
+ private PlaylistRepository playlistRepository;
+
+ private Song toAdd;
+
+ public PlaylistEditorViewModel(@NonNull Application application) {
+ super(application);
+
+ playlistRepository = new PlaylistRepository(application);
+ }
+
+ public void createPlaylist(String name) {
+ playlistRepository.createPlaylist(name, toAdd.getId());
+ }
+
+ public void updatePlaylist(String playlistId) {
+
+ }
+
+ public void setSongToAdd(Song song) {
+ toAdd = song;
+ }
+}
diff --git a/app/src/main/res/layout/bottom_sheet_song_dialog.xml b/app/src/main/res/layout/bottom_sheet_song_dialog.xml
index d04dcc36..a2e2ba2e 100644
--- a/app/src/main/res/layout/bottom_sheet_song_dialog.xml
+++ b/app/src/main/res/layout/bottom_sheet_song_dialog.xml
@@ -152,6 +152,22 @@
android:textFontWeight="700"
android:textSize="14sp" />
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_playlist_editor.xml b/app/src/main/res/layout/dialog_playlist_editor.xml
new file mode 100644
index 00000000..14257d53
--- /dev/null
+++ b/app/src/main/res/layout/dialog_playlist_editor.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_horizontal_playlist.xml b/app/src/main/res/layout/item_horizontal_playlist.xml
new file mode 100644
index 00000000..a2168dd7
--- /dev/null
+++ b/app/src/main/res/layout/item_horizontal_playlist.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file