Add option to order and delete elements from queue by dragging and swiping

This commit is contained in:
CappielloAntonio 2021-04-16 18:00:19 +02:00
parent 49c3d60ed1
commit a2770daaa8
9 changed files with 70 additions and 111 deletions

View file

@ -9,12 +9,10 @@ import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.play.App;
import com.cappielloantonio.play.R; import com.cappielloantonio.play.R;
import com.cappielloantonio.play.glide.CustomGlideRequest; import com.cappielloantonio.play.glide.CustomGlideRequest;
import com.cappielloantonio.play.helper.MusicPlayerRemote; import com.cappielloantonio.play.helper.MusicPlayerRemote;
import com.cappielloantonio.play.model.Song; import com.cappielloantonio.play.model.Song;
import com.cappielloantonio.play.repository.SongRepository;
import com.cappielloantonio.play.ui.fragment.PlayerBottomSheetFragment; import com.cappielloantonio.play.ui.fragment.PlayerBottomSheetFragment;
import java.util.ArrayList; import java.util.ArrayList;

View file

@ -23,7 +23,7 @@ import com.cappielloantonio.play.model.RecentSearch;
import com.cappielloantonio.play.model.Song; import com.cappielloantonio.play.model.Song;
import com.cappielloantonio.play.model.SongGenreCross; import com.cappielloantonio.play.model.SongGenreCross;
@Database(entities = {Album.class, Artist.class, Genre.class, Playlist.class, Song.class, RecentSearch.class, SongGenreCross.class, Queue.class}, version = 6, exportSchema = false) @Database(entities = {Album.class, Artist.class, Genre.class, Playlist.class, Song.class, RecentSearch.class, SongGenreCross.class, Queue.class}, version = 7, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase { public abstract class AppDatabase extends RoomDatabase {
private static final String TAG = "AppDatabase"; private static final String TAG = "AppDatabase";

View file

@ -20,18 +20,6 @@ public interface QueueDao {
@Query("SELECT * FROM song JOIN queue ON song.id = queue.id") @Query("SELECT * FROM song JOIN queue ON song.id = queue.id")
List<Song> getAllSimple(); List<Song> getAllSimple();
@Query("SELECT * FROM song JOIN queue ON song.id = queue.id WHERE queue.rowid = :position")
Song getSongByIndex(int position);
@Query("SELECT * FROM song JOIN queue ON song.id = queue.id WHERE queue.last_played != 0 ORDER BY queue.last_played DESC LIMIT 1")
LiveData<Song> getLastPlayedSong();
@Query("UPDATE queue SET last_played = :timestamp WHERE queue.rowid = :position")
void setLastPlayedSong(int position, long timestamp);
@Query("UPDATE queue SET last_played = :timestamp WHERE queue.id = :songID")
void setLastPlayedSong(String songID, long timestamp);
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(Queue songQueueObject); void insert(Queue songQueueObject);
@ -41,7 +29,7 @@ public interface QueueDao {
@Delete @Delete
void delete(Queue songQueueObject); void delete(Queue songQueueObject);
@Query("DELETE FROM queue WHERE queue.rowid = :position") @Query("DELETE FROM queue WHERE queue.track_order = :position")
void deleteByPosition(int position); void deleteByPosition(int position);
@Query("DELETE FROM queue") @Query("DELETE FROM queue")

View file

@ -12,12 +12,12 @@ public class Queue {
@ColumnInfo(name = "id") @ColumnInfo(name = "id")
private String songID; private String songID;
@ColumnInfo(name = "last_played") @ColumnInfo(name = "track_order")
private long lastPlayed; private int trackOrder;
public Queue(String songID, long lastPlayed) { public Queue(String songID, int trackOrder) {
this.songID = songID; this.songID = songID;
this.lastPlayed = lastPlayed; this.trackOrder = trackOrder;
} }
public String getSongID() { public String getSongID() {
@ -28,11 +28,11 @@ public class Queue {
this.songID = songID; this.songID = songID;
} }
public long getLastPlayed() { public int getTrackOrder() {
return lastPlayed; return trackOrder;
} }
public void setLastPlayed(long lastPlayed) { public void setTrackOrder(int trackOrder) {
this.lastPlayed = lastPlayed; this.trackOrder = trackOrder;
} }
} }

View file

@ -19,7 +19,6 @@ public class QueueRepository {
private QueueDao queueDao; private QueueDao queueDao;
private LiveData<List<Song>> listLiveQueue; private LiveData<List<Song>> listLiveQueue;
private LiveData<Song> liveLastPlayedSong;
public QueueRepository(Application application) { public QueueRepository(Application application) {
AppDatabase database = AppDatabase.getInstance(application); AppDatabase database = AppDatabase.getInstance(application);
@ -31,34 +30,6 @@ public class QueueRepository {
return listLiveQueue; return listLiveQueue;
} }
public Song getSongByPosition(int position) {
Song song = null;
GetSongByPositionThreadSafe getSong = new GetSongByPositionThreadSafe(queueDao, position);
Thread thread = new Thread(getSong);
thread.start();
try {
thread.join();
song = getSong.getSong();
} catch (InterruptedException e) {
e.printStackTrace();
}
return song;
}
public LiveData<Song> getLiveLastPlayedSong() {
liveLastPlayedSong = queueDao.getLastPlayedSong();
return liveLastPlayedSong;
}
public void setLiveLastPlayedSong(Song song, int position) {
SetLastPlayedSongThreadSafe update = new SetLastPlayedSongThreadSafe(queueDao, song, position);
Thread thread = new Thread(update);
thread.start();
}
public List<Song> getSongs() { public List<Song> getSongs() {
List<Song> songs = new ArrayList<>(); List<Song> songs = new ArrayList<>();
@ -245,44 +216,4 @@ public class QueueRepository {
return songs; return songs;
} }
} }
private static class SetLastPlayedSongThreadSafe implements Runnable {
private QueueDao queueDao;
private Song song;
private int position;
public SetLastPlayedSongThreadSafe(QueueDao queueDao, Song song, int position) {
this.queueDao = queueDao;
this.song = song;
this.position = position;
}
@Override
public void run() {
if(song != null)
queueDao.setLastPlayedSong(song.getId(), Instant.now().toEpochMilli());
else
queueDao.setLastPlayedSong(position, Instant.now().toEpochMilli());
}
}
private static class GetSongByPositionThreadSafe implements Runnable {
private QueueDao queueDao;
private int position;
private Song song;
public GetSongByPositionThreadSafe(QueueDao queueDao, int position) {
this.queueDao = queueDao;
this.position = position;
}
@Override
public void run() {
song = queueDao.getSongByIndex(position);
}
public Song getSong() {
return song;
}
}
} }

View file

@ -3,7 +3,6 @@ package com.cappielloantonio.play.ui.fragment;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -145,29 +144,62 @@ public class PlayerBottomSheetFragment extends Fragment implements MusicServiceE
playerBottomSheetViewModel.getQueueSong().observe(requireActivity(), songs -> playerSongQueueAdapter.setItems(songs)); playerBottomSheetViewModel.getQueueSong().observe(requireActivity(), songs -> playerSongQueueAdapter.setItems(songs));
new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT) { new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT) {
int originalPosition = -1;
int fromPosition = -1;
int toPosition = -1;
@Override @Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
int fromPosition = viewHolder.getBindingAdapterPosition(); if(originalPosition == -1) originalPosition = viewHolder.getBindingAdapterPosition();
int toPosition = target.getBindingAdapterPosition();
fromPosition = viewHolder.getBindingAdapterPosition();
toPosition = target.getBindingAdapterPosition();
/*
* Per spostare un elemento nella coda devo:
* - Spostare graficamente la traccia da una posizione all'altra con Collections.swap()
* - Spostare nel db la traccia, tramite QueueRepository
* - Notificare il Service dell'avvenuto spostamento con MusicPlayerRemote.moveSong()
*
* In onMove prendo la posizione di inizio e fine, ma solo al rilascio dell'elemento procedo allo spostamento
* In questo modo evito che ad ogni cambio di posizione vada a riscrivere nel db
* Al rilascio dell'elemento chiamo il metodo clearView()
*/
Collections.swap(playerSongQueueAdapter.getItems(), fromPosition, toPosition); Collections.swap(playerSongQueueAdapter.getItems(), fromPosition, toPosition);
recyclerView.getAdapter().notifyItemMoved(fromPosition, toPosition);
bind.playerBodyLayout.playerQueueRecyclerView.getAdapter().notifyItemMoved(fromPosition, toPosition);
// bind.playerBodyLayout.playerSongCoverViewPager.getAdapter().notifyItemMoved(fromPosition, toPosition);
// bind.playerBodyLayout.playerQueueRecyclerView.getAdapter().notifyDataSetChanged();
// bind.playerBodyLayout.playerSongCoverViewPager.getAdapter().notifyDataSetChanged();
// MusicPlayerRemote.moveSong(fromPosition, toPosition);
return false; return false;
} }
@Override @Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
Log.i(TAG, "onSwiped: " + viewHolder.getBindingAdapterPosition()); super.clearView(recyclerView, viewHolder);
Log.i(TAG, "onSwiped: " + MusicPlayerRemote.getPlayingQueue().get(viewHolder.getBindingAdapterPosition()).getTitle());
/*
* 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
*/
playerBottomSheetViewModel.orderSongAfterSwap(playerSongQueueAdapter.getItems());
MusicPlayerRemote.moveSong(originalPosition, toPosition);
bind.playerBodyLayout.playerSongCoverViewPager.setCurrentItem(MusicPlayerRemote.getPosition(), true);
originalPosition = -1;
fromPosition = -1;
toPosition = -1;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
if(!(viewHolder.getBindingAdapterPosition() == MusicPlayerRemote.getPosition()) && !(MusicPlayerRemote.getPlayingQueue().size() <= 1)) {
MusicPlayerRemote.removeFromQueue(viewHolder.getBindingAdapterPosition());
playerBottomSheetViewModel.removeSong(viewHolder.getBindingAdapterPosition()); playerBottomSheetViewModel.removeSong(viewHolder.getBindingAdapterPosition());
bind.playerBodyLayout.playerQueueRecyclerView.getAdapter().notifyItemRemoved(viewHolder.getBindingAdapterPosition());
bind.playerBodyLayout.playerSongCoverViewPager.setCurrentItem(MusicPlayerRemote.getPosition(), true);
}
else {
bind.playerBodyLayout.playerQueueRecyclerView.getAdapter().notifyDataSetChanged();
}
} }
} }
).attachToRecyclerView(bind.playerBodyLayout.playerQueueRecyclerView); ).attachToRecyclerView(bind.playerBodyLayout.playerQueueRecyclerView);
@ -264,6 +296,10 @@ public class PlayerBottomSheetFragment extends Fragment implements MusicServiceE
setSongInfo(song); setSongInfo(song);
} }
private void setCurrentItemInSlideView(boolean smoothScroll) {
}
@Override @Override
public void onServiceConnected() { public void onServiceConnected() {
setSongInfo(MusicPlayerRemote.getCurrentSong()); setSongInfo(MusicPlayerRemote.getCurrentSong());

View file

@ -12,10 +12,12 @@ public class QueueUtil {
} }
public static List<Queue> getQueueElementsFromSongs(List<Song> songs) { public static List<Queue> getQueueElementsFromSongs(List<Song> songs) {
int counter = 0;
List<Queue> queue = new ArrayList<>(); List<Queue> queue = new ArrayList<>();
for(Song song: songs) { for(Song song: songs) {
queue.add(new Queue(song.getId(), 0)); queue.add(new Queue(song.getId(), counter));
counter++;
} }
return queue; return queue;

View file

@ -50,6 +50,10 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
this.song = song; this.song = song;
} }
public void orderSongAfterSwap(List<Song> songs) {
queueRepository.insertAllAndStartNew(songs);
}
public void removeSong(int position) { public void removeSong(int position) {
queueRepository.deleteByPosition(position); queueRepository.deleteByPosition(position);
} }

View file

@ -41,7 +41,7 @@
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintStart_toEndOf="@+id/queue_song_cover_image_view" app:layout_constraintStart_toEndOf="@+id/queue_song_cover_image_view"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@+id/queue_song_dragger_image"/> app:layout_constraintEnd_toStartOf="@+id/queue_song_holder_image"/>
<TextView <TextView
android:id="@+id/queue_song_artist_text_view" android:id="@+id/queue_song_artist_text_view"
@ -54,10 +54,10 @@
android:textSize="12sp" android:textSize="12sp"
app:layout_constraintTop_toBottomOf="@+id/queue_song_title_text_view" app:layout_constraintTop_toBottomOf="@+id/queue_song_title_text_view"
app:layout_constraintStart_toEndOf="@+id/queue_song_cover_image_view" app:layout_constraintStart_toEndOf="@+id/queue_song_cover_image_view"
app:layout_constraintEnd_toStartOf="@+id/queue_song_dragger_image"/> app:layout_constraintEnd_toStartOf="@+id/queue_song_holder_image"/>
<ImageView <ImageView
android:id="@+id/queue_song_dragger_image" android:id="@+id/queue_song_holder_image"
android:layout_width="36dp" android:layout_width="36dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:paddingStart="8dp" android:paddingStart="8dp"