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