Implemented playlist

This commit is contained in:
CappielloAntonio 2021-04-18 19:06:07 +02:00
parent d72b37725c
commit 3addc3b561
14 changed files with 255 additions and 5 deletions

View file

@ -39,6 +39,7 @@ dependencies {
// Jellyfin
implementation 'com.github.jellyfin.jellyfin-apiclient-java:android:0.7.7'
// implementation "org.jellyfin.sdk:jellyfin-platform-android:1.0.0-beta.3"
// Kotlin
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.4.32'

View file

@ -11,6 +11,7 @@ import com.cappielloantonio.play.database.dao.AlbumDao;
import com.cappielloantonio.play.database.dao.ArtistDao;
import com.cappielloantonio.play.database.dao.GenreDao;
import com.cappielloantonio.play.database.dao.PlaylistDao;
import com.cappielloantonio.play.database.dao.PlaylistSongCrossDao;
import com.cappielloantonio.play.database.dao.QueueDao;
import com.cappielloantonio.play.database.dao.RecentSearchDao;
import com.cappielloantonio.play.database.dao.SongArtistCrossDao;
@ -21,13 +22,14 @@ import com.cappielloantonio.play.model.AlbumArtistCross;
import com.cappielloantonio.play.model.Artist;
import com.cappielloantonio.play.model.Genre;
import com.cappielloantonio.play.model.Playlist;
import com.cappielloantonio.play.model.PlaylistSongCross;
import com.cappielloantonio.play.model.Queue;
import com.cappielloantonio.play.model.RecentSearch;
import com.cappielloantonio.play.model.Song;
import com.cappielloantonio.play.model.SongArtistCross;
import com.cappielloantonio.play.model.SongGenreCross;
@Database(entities = {Album.class, Artist.class, Genre.class, Playlist.class, Song.class, RecentSearch.class, SongGenreCross.class, Queue.class, AlbumArtistCross.class, SongArtistCross.class}, version = 10, exportSchema = false)
@Database(entities = {Album.class, Artist.class, Genre.class, Playlist.class, Song.class, RecentSearch.class, SongGenreCross.class, Queue.class, AlbumArtistCross.class, SongArtistCross.class, PlaylistSongCross.class}, version = 10, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {
private static final String TAG = "AppDatabase";
@ -62,5 +64,7 @@ public abstract class AppDatabase extends RoomDatabase {
public abstract SongArtistCrossDao songArtistCrossDao();
public abstract PlaylistSongCrossDao playlistSongCrossDao();
public abstract QueueDao queueDao();
}

View file

@ -0,0 +1,38 @@
package com.cappielloantonio.play.database.dao;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.Update;
import com.cappielloantonio.play.model.PlaylistSongCross;
import com.cappielloantonio.play.model.SongArtistCross;
import java.util.List;
@Dao
public interface PlaylistSongCrossDao {
@Query("SELECT * FROM playlist_song_cross")
LiveData<List<PlaylistSongCross>> getAll();
@Query("SELECT EXISTS(SELECT * FROM playlist_song_cross WHERE id = :id)")
boolean exist(String id);
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(PlaylistSongCross playlistSongCross);
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertAll(List<PlaylistSongCross> playlistSongCrosses);
@Delete
void delete(PlaylistSongCross playlistSongCross);
@Update
void update(PlaylistSongCross playlistSongCross);
@Query("DELETE FROM playlist_song_cross")
void deleteAll();
}

View file

@ -51,6 +51,10 @@ public interface SongDao {
@Query("SELECT * FROM song WHERE albumId = :albumID ORDER BY trackNumber ASC")
LiveData<List<Song>> getLiveAlbumSong(String albumID);
// @Query("SELECT * FROM song WHERE albumId = :albumID ORDER BY trackNumber ASC")
@Query("SELECT song.* FROM song INNER JOIN playlist_song_cross ON song.id = playlist_song_cross.song_id AND playlist_song_cross.playlist_id = :playlistID")
LiveData<List<Song>> getLivePlaylistSong(String playlistID);
@Query("SELECT * FROM song WHERE albumId = :albumID ORDER BY trackNumber ASC")
List<Song> getAlbumSong(String albumID);

View file

@ -0,0 +1,49 @@
package com.cappielloantonio.play.model;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "playlist_song_cross")
public class PlaylistSongCross {
@NonNull
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id")
private int id;
@ColumnInfo(name = "playlist_id")
private String playlistId;
@ColumnInfo(name = "song_id")
private String songId;
public PlaylistSongCross(String playlistId, String songId) {
this.playlistId = playlistId;
this.songId = songId;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getPlaylistId() {
return playlistId;
}
public void setPlaylistId(String playlistId) {
this.playlistId = playlistId;
}
public String getSongId() {
return songId;
}
public void setSongId(String songId) {
this.songId = songId;
}
}

View file

@ -0,0 +1,62 @@
package com.cappielloantonio.play.repository;
import android.app.Application;
import com.cappielloantonio.play.database.AppDatabase;
import com.cappielloantonio.play.database.dao.PlaylistSongCrossDao;
import com.cappielloantonio.play.database.dao.SongArtistCrossDao;
import com.cappielloantonio.play.model.PlaylistSongCross;
import com.cappielloantonio.play.model.SongArtistCross;
import java.util.List;
public class PlaylistSongRepository {
private static final String TAG = "AlbumArtistRepository";
private PlaylistSongCrossDao playlistSongCrossDao;
public PlaylistSongRepository(Application application) {
AppDatabase database = AppDatabase.getInstance(application);
playlistSongCrossDao = database.playlistSongCrossDao();
}
public void insertAll(List<PlaylistSongCross> crosses) {
InsertAllThreadSafe insertAll = new InsertAllThreadSafe(playlistSongCrossDao, crosses);
Thread thread = new Thread(insertAll);
thread.start();
}
private static class InsertAllThreadSafe implements Runnable {
private PlaylistSongCrossDao playlistSongCrossDao;
private List<PlaylistSongCross> crosses;
public InsertAllThreadSafe(PlaylistSongCrossDao playlistSongCrossDao, List<PlaylistSongCross> crosses) {
this.playlistSongCrossDao = playlistSongCrossDao;
this.crosses = crosses;
}
@Override
public void run() {
playlistSongCrossDao.insertAll(crosses);
}
}
public void deleteAll() {
DeleteAllPlaylistSongCrossThreadSafe delete = new DeleteAllPlaylistSongCrossThreadSafe(playlistSongCrossDao);
Thread thread = new Thread(delete);
thread.start();
}
private static class DeleteAllPlaylistSongCrossThreadSafe implements Runnable {
private PlaylistSongCrossDao playlistSongCrossDao;
public DeleteAllPlaylistSongCrossThreadSafe(PlaylistSongCrossDao playlistSongCrossDao) {
this.playlistSongCrossDao = playlistSongCrossDao;
}
@Override
public void run() {
playlistSongCrossDao.deleteAll();
}
}
}

View file

@ -27,6 +27,7 @@ public class SongRepository {
private LiveData<List<Song>> listLiveSampleMostPlayedSongs;
private LiveData<List<Song>> listLiveSampleArtistTopSongs;
private LiveData<List<Song>> listLiveAlbumSongs;
private LiveData<List<Song>> listLivePlaylistSongs;
private LiveData<List<Song>> listLiveSongByGenre;
private LiveData<List<Song>> listLiveFilteredSongs;
private LiveData<List<Song>> listLiveSongByYear;
@ -102,6 +103,11 @@ public class SongRepository {
return listLiveAlbumSongs;
}
public LiveData<List<Song>> getPlaylistLiveSong(String playlistID) {
listLivePlaylistSongs = songDao.getLivePlaylistSong(playlistID);
return listLivePlaylistSongs;
}
public List<Song> getAlbumListSong(String albumID, boolean randomOrder) {
List<Song> songs = new ArrayList<>();

View file

@ -45,6 +45,7 @@ public class PlaylistPageFragment extends Fragment {
playlistPageViewModel = new ViewModelProvider(requireActivity()).get(PlaylistPageViewModel.class);
init();
initBackCover();
initSongsView();
return view;
@ -82,7 +83,13 @@ public class PlaylistPageFragment extends Fragment {
bind.animToolbar.getNavigationIcon().setColorFilter(getResources().getColor(R.color.white, null), PorterDuff.Mode.SRC_ATOP);
}
});
}
private void initBackCover() {
CustomGlideRequest.Builder
.from(requireContext(), playlistPageViewModel.getPlaylist().getPrimary(), playlistPageViewModel.getPlaylist().getBlurHash(), CustomGlideRequest.PRIMARY, CustomGlideRequest.TOP_QUALITY, CustomGlideRequest.ALBUM_PIC)
.build()
.into(bind.albumBackCoverImageView);
}
private void initSongsView() {
@ -91,6 +98,8 @@ public class PlaylistPageFragment extends Fragment {
songResultSearchAdapter = new SongResultSearchAdapter(activity, requireContext(), getChildFragmentManager());
bind.songRecyclerView.setAdapter(songResultSearchAdapter);
playlistPageViewModel.getPlaylistSongList().observe(requireActivity(), songs -> songResultSearchAdapter.setItems(songs));
playlistPageViewModel.getPlaylistSongList().observe(requireActivity(), songs -> {
songResultSearchAdapter.setItems(songs);
});
}
}

View file

@ -74,8 +74,8 @@ public class SettingsFragment extends PreferenceFragmentCompat {
Preference cross_sync_button = findPreference(getString(R.string.genres_music_cross_sync));
cross_sync_button.setOnPreferenceClickListener(preference -> {
AlertDialog.Builder builder = new AlertDialog.Builder(requireContext());
builder.setMessage("Sync song's genres otherwise nothing will be shown in each genre category")
.setTitle("Song's genres not synchronized")
builder.setMessage("Force sync song's genres to display updated and correct songs in each genre category")
.setTitle("Force sync song's genres")
.setNegativeButton(R.string.ignore, null)
.setPositiveButton("Sync", (dialog, id) -> {
Bundle bundle = SyncUtil.getSyncBundle(false, false, true, false, false, true);
@ -84,6 +84,20 @@ public class SettingsFragment extends PreferenceFragmentCompat {
.show();
return true;
});
Preference playlist_sync_button = findPreference(getString(R.string.playlist_song_cross_sync));
playlist_sync_button.setOnPreferenceClickListener(preference -> {
AlertDialog.Builder builder = new AlertDialog.Builder(requireContext());
builder.setMessage("Sync playlists' songs")
.setTitle("Force sync playlist")
.setNegativeButton(R.string.ignore, null)
.setPositiveButton("Sync", (dialog, id) -> {
Bundle bundle = SyncUtil.getSyncBundle(false, false, false, true, false, false);
activity.goFromSettingsToSync(bundle);
})
.show();
return true;
});
}
}

View file

@ -20,6 +20,7 @@ import com.cappielloantonio.play.model.AlbumArtistCross;
import com.cappielloantonio.play.model.Artist;
import com.cappielloantonio.play.model.Genre;
import com.cappielloantonio.play.model.Playlist;
import com.cappielloantonio.play.model.PlaylistSongCross;
import com.cappielloantonio.play.model.Song;
import com.cappielloantonio.play.model.SongArtistCross;
import com.cappielloantonio.play.model.SongGenreCross;
@ -28,6 +29,7 @@ import com.cappielloantonio.play.repository.AlbumRepository;
import com.cappielloantonio.play.repository.ArtistRepository;
import com.cappielloantonio.play.repository.GenreRepository;
import com.cappielloantonio.play.repository.PlaylistRepository;
import com.cappielloantonio.play.repository.PlaylistSongRepository;
import com.cappielloantonio.play.repository.SongArtistRepository;
import com.cappielloantonio.play.repository.SongRepository;
import com.cappielloantonio.play.ui.activities.MainActivity;
@ -54,6 +56,7 @@ public class SyncFragment extends Fragment {
private GenreRepository genreRepository;
private SongArtistRepository songArtistRepository;
private AlbumArtistRepository albumArtistRepository;
private PlaylistSongRepository playlistSongRepository;
@Nullable
@Override
@ -71,6 +74,7 @@ public class SyncFragment extends Fragment {
genreRepository = new GenreRepository(activity.getApplication());
songArtistRepository = new SongArtistRepository(activity.getApplication());
albumArtistRepository = new AlbumArtistRepository(activity.getApplication());
playlistSongRepository = new PlaylistSongRepository(activity.getApplication());
init();
syncLibraries();
@ -181,6 +185,7 @@ public class SyncFragment extends Fragment {
@Override
public void onLoadMedia(List<?> media) {
playlistRepository.insertAll((ArrayList<Playlist>) media);
syncSongsPerPlaylist((ArrayList<Playlist>) media);
animateProgressBar(true);
}
});
@ -223,6 +228,22 @@ public class SyncFragment extends Fragment {
PreferenceUtil.getInstance(requireContext()).setSongGenreSync(true);
}
private void syncSongsPerPlaylist(List<Playlist> playlists) {
for (Playlist playlist : playlists) {
SyncUtil.getSongsPerPlaylist(requireContext(), new MediaCallback() {
@Override
public void onError(Exception exception) {
Log.e(TAG, "onError: " + exception.getMessage());
}
@Override
public void onLoadMedia(List<?> media) {
playlistSongRepository.insertAll((ArrayList<PlaylistSongCross>) media);
}
}, playlist.getId());
}
}
private void animateProgressBar(boolean step) {
syncViewModel.setProgress(step);

View file

@ -2,6 +2,7 @@ package com.cappielloantonio.play.util;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import com.cappielloantonio.play.App;
import com.cappielloantonio.play.interfaces.MediaCallback;
@ -9,6 +10,7 @@ import com.cappielloantonio.play.model.Album;
import com.cappielloantonio.play.model.Artist;
import com.cappielloantonio.play.model.Genre;
import com.cappielloantonio.play.model.Playlist;
import com.cappielloantonio.play.model.PlaylistSongCross;
import com.cappielloantonio.play.model.Song;
import com.cappielloantonio.play.model.SongGenreCross;
@ -19,6 +21,7 @@ import org.jellyfin.apiclient.model.querying.ItemFields;
import org.jellyfin.apiclient.model.querying.ItemQuery;
import org.jellyfin.apiclient.model.querying.ItemsByNameQuery;
import org.jellyfin.apiclient.model.querying.ItemsResult;
import org.jellyfin.apiclient.model.playlists.PlaylistItemQuery;
import java.util.ArrayList;
import java.util.Arrays;
@ -207,6 +210,34 @@ public class SyncUtil {
});
}
public static void getSongsPerPlaylist(Context context, MediaCallback callback, String playlistId) {
PlaylistItemQuery query = new PlaylistItemQuery();
query.setId(playlistId);
query.setUserId(App.getApiClientInstance(context).getCurrentUserId());
query.setFields(new ItemFields[]{ItemFields.MediaSources});
query.setUserId(App.getApiClientInstance(context).getCurrentUserId());
App.getApiClientInstance(context).GetPlaylistItems(query, new Response<ItemsResult>() {
@Override
public void onResponse(ItemsResult result) {
ArrayList<PlaylistSongCross> crosses = new ArrayList<>();
for (BaseItemDto itemDto : result.getItems()) {
crosses.add(new PlaylistSongCross(playlistId, itemDto.getId()));
}
callback.onLoadMedia(crosses);
}
@Override
public void onError(Exception exception) {
callback.onError(exception);
}
});
}
public static Bundle getSyncBundle(Boolean syncAlbum, Boolean syncArtist, Boolean syncGenres, Boolean syncPlaylist, Boolean syncSong, Boolean crossSyncSongGenre) {
Bundle bundle = new Bundle();
bundle.putBoolean("sync_album", syncAlbum);

View file

@ -1,6 +1,7 @@
package com.cappielloantonio.play.viewmodel;
import android.app.Application;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
@ -14,6 +15,8 @@ import com.cappielloantonio.play.repository.SongRepository;
import java.util.List;
public class PlaylistPageViewModel extends AndroidViewModel {
private static final String TAG = "PlaylistPageViewModel";
private SongRepository songRepository;
private LiveData<List<Song>> songList;
@ -28,7 +31,8 @@ public class PlaylistPageViewModel extends AndroidViewModel {
public LiveData<List<Song>> getPlaylistSongList() {
// Prendere le canzoni di ciascuna playlist
songList = songRepository.getAlbumListLiveSong(playlist.getId());
Log.i(TAG, "getPlaylistSongList: " + playlist.getId());
songList = songRepository.getPlaylistLiveSong(playlist.getId());
return songList;
}

View file

@ -19,6 +19,8 @@
<string name="music_sync_caption">Sync tracks, albums, artists, genres and playlists</string>
<string name="genres_music_cross_sync">Genres-music cross sync</string>
<string name="genres_music_cross_sync_caption">Cross sync genres for each track</string>
<string name="playlist_song_cross_sync">Playlist sync</string>
<string name="playlist_song_cross_sync_caption">Refresh playlists\' track list</string>
<string name="about_header">About</string>
<string name="summary_about">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</string>
<string name="app_version">1.1</string>

View file

@ -40,6 +40,11 @@
android:key="@string/genres_music_cross_sync"
android:summary="@string/genres_music_cross_sync_caption" />
<Preference
android:title="@string/playlist_song_cross_sync"
android:key="@string/playlist_song_cross_sync"
android:summary="@string/playlist_song_cross_sync_caption" />
</PreferenceCategory>
<PreferenceCategory app:title="@string/about_header">