Add playlist catalogue page and item

This commit is contained in:
CappielloAntonio 2021-04-20 15:59:21 +02:00
parent 717986ea2c
commit ee8509032a
15 changed files with 411 additions and 60 deletions

View file

@ -5,14 +5,17 @@ import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.play.R;
import com.cappielloantonio.play.glide.CustomGlideRequest;
import com.cappielloantonio.play.model.Album;
import com.cappielloantonio.play.model.Playlist;
import com.cappielloantonio.play.ui.activities.MainActivity;
import java.util.ArrayList;
import java.util.List;
@ -23,11 +26,13 @@ public class PlaylistAdapter extends RecyclerView.Adapter<PlaylistAdapter.ViewHo
private List<Playlist> playlists;
private LayoutInflater mInflater;
private Context context;
private MainActivity activity;
public PlaylistAdapter(Context context) {
public PlaylistAdapter(MainActivity activity, Context context, List<Playlist> playlists) {
this.activity = activity;
this.context = context;
this.mInflater = LayoutInflater.from(context);
this.playlists = new ArrayList<>();
this.playlists = playlists;
}
@Override
@ -41,6 +46,11 @@ public class PlaylistAdapter extends RecyclerView.Adapter<PlaylistAdapter.ViewHo
Playlist playlist = playlists.get(position);
holder.textPlaylistName.setText(playlist.getName());
CustomGlideRequest.Builder
.from(context, playlist.getPrimary(), playlist.getBlurHash(), CustomGlideRequest.PRIMARY, CustomGlideRequest.TOP_QUALITY, CustomGlideRequest.PLAYLIST_PIC)
.build()
.into(holder.cover);
}
@Override
@ -50,11 +60,13 @@ public class PlaylistAdapter extends RecyclerView.Adapter<PlaylistAdapter.ViewHo
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView textPlaylistName;
ImageView cover;
ViewHolder(View itemView) {
super(itemView);
textPlaylistName = itemView.findViewById(R.id.playlist_name_text);
cover = itemView.findViewById(R.id.playlist_cover_image_view);
itemView.setOnClickListener(this);
}

View file

@ -0,0 +1,87 @@
package com.cappielloantonio.play.adapter;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.play.R;
import com.cappielloantonio.play.glide.CustomGlideRequest;
import com.cappielloantonio.play.model.Genre;
import com.cappielloantonio.play.model.Playlist;
import java.util.ArrayList;
import java.util.List;
public class PlaylistCatalogueAdapter extends RecyclerView.Adapter<PlaylistCatalogueAdapter.ViewHolder> {
private List<Playlist> playlists;
private LayoutInflater mInflater;
private Context context;
public PlaylistCatalogueAdapter(Context context) {
this.context = context;
this.mInflater = LayoutInflater.from(context);
this.playlists = new ArrayList<>();
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.item_library_catalogue_playlist, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Playlist playlist = playlists.get(position);
holder.textPlaylistName.setText(playlist.getName());
CustomGlideRequest.Builder
.from(context, playlist.getPrimary(), playlist.getBlurHash(), CustomGlideRequest.PRIMARY, CustomGlideRequest.TOP_QUALITY, CustomGlideRequest.PLAYLIST_PIC)
.build()
.into(holder.cover);
}
@Override
public int getItemCount() {
return playlists.size();
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView textPlaylistName;
ImageView cover;
ViewHolder(View itemView) {
super(itemView);
textPlaylistName = itemView.findViewById(R.id.playlist_name_text);
cover = itemView.findViewById(R.id.playlist_cover_image_view);
itemView.setOnClickListener(this);
}
@Override
public void onClick(View view) {
Bundle bundle = new Bundle();
bundle.putParcelable("playlist_object", playlists.get(getBindingAdapterPosition()));
Navigation.findNavController(view).navigate(R.id.action_playlistCatalogueFragment_to_playlistPageFragment, bundle);
}
}
public Playlist getItem(int position) {
return playlists.get(position);
}
public void setItems(List<Playlist> playlists) {
this.playlists = playlists;
notifyDataSetChanged();
}
}

View file

@ -8,6 +8,7 @@ import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import com.cappielloantonio.play.model.Playlist;
import com.cappielloantonio.play.model.Song;
import java.util.List;
@ -21,4 +22,7 @@ public interface PlaylistDao {
@Query("DELETE FROM playlist")
void deleteAll();
@Query("SELECT * FROM playlist ORDER BY RANDOM() LIMIT :number")
List<Playlist> random(int number);
}

View file

@ -6,7 +6,9 @@ import androidx.lifecycle.LiveData;
import com.cappielloantonio.play.database.AppDatabase;
import com.cappielloantonio.play.database.dao.PlaylistDao;
import com.cappielloantonio.play.database.dao.SongDao;
import com.cappielloantonio.play.model.Playlist;
import com.cappielloantonio.play.model.Song;
import java.util.ArrayList;
import java.util.List;
@ -65,4 +67,41 @@ public class PlaylistRepository {
playlistDao.deleteAll();
}
}
public List<Playlist> getRandomSample(int number) {
List<Playlist> sample = new ArrayList<>();
PickRandomThreadSafe randomThread = new PickRandomThreadSafe(playlistDao, number);
Thread thread = new Thread(randomThread);
thread.start();
try {
thread.join();
sample = randomThread.getSample();
} catch (InterruptedException e) {
e.printStackTrace();
}
return sample;
}
private static class PickRandomThreadSafe implements Runnable {
private PlaylistDao playlistDao;
private int elementNumber;
private List<Playlist> sample;
public PickRandomThreadSafe(PlaylistDao playlistDao, int number) {
this.playlistDao = playlistDao;
this.elementNumber = number;
}
@Override
public void run() {
sample = playlistDao.random(elementNumber);
}
public List<Playlist> getSample() {
return sample;
}
}
}

View file

@ -244,7 +244,7 @@ public class MainActivity extends BaseActivity {
if (Objects.requireNonNull(navController.getCurrentDestination()).getId() == R.id.landingFragment) {
navController.navigate(R.id.action_landingFragment_to_homeFragment);
} else if (Objects.requireNonNull(navController.getCurrentDestination()).getId() == R.id.syncFragment) {
navController.navigate(R.id.action_syncFragment_to_libraryFragment);
navController.navigate(R.id.action_syncFragment_to_homeFragment);
} else if (Objects.requireNonNull(navController.getCurrentDestination()).getId() == R.id.loginFragment) {
navController.navigate(R.id.action_loginFragment_to_homeFragment);
}

View file

@ -1,6 +1,7 @@
package com.cappielloantonio.play.ui.fragment;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -9,19 +10,23 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.viewpager2.widget.ViewPager2;
import com.cappielloantonio.play.R;
import com.cappielloantonio.play.adapter.AlbumAdapter;
import com.cappielloantonio.play.adapter.ArtistAdapter;
import com.cappielloantonio.play.adapter.DiscoverSongAdapter;
import com.cappielloantonio.play.adapter.GenreAdapter;
import com.cappielloantonio.play.adapter.PlaylistAdapter;
import com.cappielloantonio.play.databinding.FragmentLibraryBinding;
import com.cappielloantonio.play.model.Song;
import com.cappielloantonio.play.ui.activities.MainActivity;
import com.cappielloantonio.play.util.MusicUtil;
import com.cappielloantonio.play.util.PreferenceUtil;
import com.cappielloantonio.play.viewmodel.LibraryViewModel;
@ -58,7 +63,7 @@ public class LibraryFragment extends Fragment {
initAlbumView();
initArtistView();
initGenreView();
initPlaylistView();
initPlaylistSlideView();
initCatalogueSyncCheck();
}
@ -78,6 +83,7 @@ public class LibraryFragment extends Fragment {
bind.albumCatalogueTextViewClickable.setOnClickListener(v -> activity.navController.navigate(R.id.action_libraryFragment_to_albumCatalogueFragment));
bind.artistCatalogueTextViewClickable.setOnClickListener(v -> activity.navController.navigate(R.id.action_libraryFragment_to_artistCatalogueFragment));
bind.genreCatalogueTextViewClickable.setOnClickListener(v -> activity.navController.navigate(R.id.action_libraryFragment_to_genreCatalogueFragment));
bind.playlistCatalogueTextViewClickable.setOnClickListener(v -> activity.navController.navigate(R.id.action_libraryFragment_to_playlistCatalogueFragment));
}
private void initAlbumView() {
@ -115,16 +121,15 @@ public class LibraryFragment extends Fragment {
});
}
private void initPlaylistView() {
bind.playlistRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 2));
bind.playlistRecyclerView.setHasFixedSize(true);
private void initPlaylistSlideView() {
bind.playlistViewPager.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL);
playlistAdapter = new PlaylistAdapter(requireContext());
bind.playlistRecyclerView.setAdapter(playlistAdapter);
libraryViewModel.getPlaylistList().observe(requireActivity(), playlists -> {
if(bind != null) bind.libraryPlaylistSector.setVisibility(playlists.size() > 0 ? View.VISIBLE : View.GONE);
playlistAdapter.setItems(playlists);
});
playlistAdapter = new PlaylistAdapter(activity, requireContext(), libraryViewModel.getPlaylistSample());
bind.playlistViewPager.setAdapter(playlistAdapter);
bind.playlistViewPager.setOffscreenPageLimit(3);
setDiscoverSongSlideViewOffset(20, 16);
Log.i(TAG, "initDiscoverSongSlideView: " + MusicUtil.getRandomSongNumber(requireContext(), 10, 3));
}
private void initCatalogueSyncCheck() {
@ -137,4 +142,19 @@ public class LibraryFragment extends Fragment {
.show();
}
}
private void setDiscoverSongSlideViewOffset(float pageOffset, float pageMargin) {
bind.playlistViewPager.setPageTransformer((page, position) -> {
float myOffset = position * -(2 * pageOffset + pageMargin);
if (bind.playlistViewPager.getOrientation() == ViewPager2.ORIENTATION_HORIZONTAL) {
if (ViewCompat.getLayoutDirection(bind.playlistViewPager) == ViewCompat.LAYOUT_DIRECTION_RTL) {
page.setTranslationX(-myOffset);
} else {
page.setTranslationX(myOffset);
}
} else {
page.setTranslationY(myOffset);
}
});
}
}

View file

@ -0,0 +1,71 @@
package com.cappielloantonio.play.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.play.R;
import com.cappielloantonio.play.adapter.GenreCatalogueAdapter;
import com.cappielloantonio.play.adapter.PlaylistAdapter;
import com.cappielloantonio.play.adapter.PlaylistCatalogueAdapter;
import com.cappielloantonio.play.databinding.FragmentGenreCatalogueBinding;
import com.cappielloantonio.play.databinding.FragmentPlaylistCatalogueBinding;
import com.cappielloantonio.play.helper.recyclerview.GridItemDecoration;
import com.cappielloantonio.play.model.Song;
import com.cappielloantonio.play.ui.activities.MainActivity;
import com.cappielloantonio.play.viewmodel.GenreCatalogueViewModel;
import com.cappielloantonio.play.viewmodel.PlaylistCatalogueViewModel;
import java.util.ArrayList;
public class PlaylistCatalogueFragment extends Fragment {
private static final String TAG = "GenreCatalogueFragment";;
private FragmentPlaylistCatalogueBinding bind;
private MainActivity activity;
private PlaylistCatalogueViewModel playlistCatalogueViewModel;
private PlaylistCatalogueAdapter playlistCatalogueAdapter;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentPlaylistCatalogueBinding.inflate(inflater, container, false);
View view = bind.getRoot();
playlistCatalogueViewModel = new ViewModelProvider(requireActivity()).get(PlaylistCatalogueViewModel.class);
initArtistCatalogueView();
return view;
}
@Override
public void onStart() {
super.onStart();
activity.setBottomNavigationBarVisibility(false);
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void initArtistCatalogueView() {
bind.playlistCatalogueRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.playlistCatalogueRecyclerView.setHasFixedSize(true);
playlistCatalogueAdapter = new PlaylistCatalogueAdapter(requireContext());
bind.playlistCatalogueRecyclerView.setAdapter(playlistCatalogueAdapter);
playlistCatalogueViewModel.getPlaylistList().observe(requireActivity(), playlist -> {
playlistCatalogueAdapter.setItems(playlist);
});
}
}

View file

@ -28,8 +28,4 @@ public class GenreCatalogueViewModel extends AndroidViewModel {
genreList = genreRepository.getListLiveGenres();
return genreList;
}
public List<Genre> getGenres() {
return genreRepository.getListGenre();
}
}

View file

@ -10,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.Song;
import com.cappielloantonio.play.repository.AlbumRepository;
import com.cappielloantonio.play.repository.ArtistRepository;
import com.cappielloantonio.play.repository.GenreRepository;
@ -24,10 +25,10 @@ public class LibraryViewModel extends AndroidViewModel {
private GenreRepository genreRepository;
private PlaylistRepository playlistRepository;
private List<Playlist> playlistSample;
private LiveData<List<Album>> sampleAlbum;
private LiveData<List<Artist>> sampleArtist;
private LiveData<List<Genre>> sampleGenres;
private LiveData<List<Playlist>> allPlaylist;
public LibraryViewModel(@NonNull Application application) {
super(application);
@ -42,11 +43,15 @@ public class LibraryViewModel extends AndroidViewModel {
sampleAlbum = albumRepository.getListLiveSampleAlbum();
sampleArtist = artistRepository.getListLiveSampleArtist();
sampleGenres = genreRepository.getListLiveSampleGenre();
allPlaylist = playlistRepository.getListLivePlaylists();
playlistSample = playlistRepository.getRandomSample(5);
}
public LiveData<List<Playlist>> getPlaylistList() {
return allPlaylist;
public List<Playlist> getPlaylistSample() {
if(playlistSample.isEmpty()) {
playlistSample = playlistRepository.getRandomSample(5);
}
return playlistSample;
}
public LiveData<List<Album>> getAlbumSample() {

View file

@ -0,0 +1,31 @@
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.Genre;
import com.cappielloantonio.play.model.Playlist;
import com.cappielloantonio.play.repository.GenreRepository;
import com.cappielloantonio.play.repository.PlaylistRepository;
import java.util.List;
public class PlaylistCatalogueViewModel extends AndroidViewModel {
private PlaylistRepository playlistRepository;
private LiveData<List<Playlist>> playlistList;
public PlaylistCatalogueViewModel(@NonNull Application application) {
super(application);
playlistRepository = new PlaylistRepository(application);
}
public LiveData<List<Playlist>> getPlaylistList() {
playlistList = playlistRepository.getListLivePlaylists();
return playlistList;
}
}

View file

@ -238,6 +238,7 @@
<TextView
android:id="@+id/playlist_catalogue_text_view_clickable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/open_sans_font_family"
@ -250,16 +251,13 @@
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/playlist_recycler_view"
<!-- slideview -->
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/playlist_view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:layout_height="212dp"
android:clipToPadding="false"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="10dp"
android:paddingBottom="8dp" />
</LinearLayout>
</LinearLayout>

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Label and button -->
<TextView
android:id="@+id/playlist_catalogue_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="@font/open_sans_font_family"
android:paddingStart="16dp"
android:paddingTop="20dp"
android:paddingEnd="16dp"
android:text="Playlist Catalogue"
android:textColor="@color/titleTextColor"
android:textSize="22sp"
android:textStyle="bold" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/playlist_catalogue_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="16dp"
android:paddingBottom="@dimen/global_padding_bottom"
android:layout_below="@+id/playlist_catalogue_label"/>
</RelativeLayout>

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/playlist_card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:backgroundTint="@color/cardColor"
app:cardCornerRadius="4dp">
<ImageView
android:id="@+id/playlist_cover_image_view"
android:layout_width="match_parent"
android:layout_height="196dp" />
<View
android:layout_width="match_parent"
android:layout_height="196dp"
android:background="@drawable/gradient_discover_background_image" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="196dp">
<TextView
android:id="@+id/playlist_name_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginTop="18dp"
android:layout_marginRight="16dp"
android:ellipsize="end"
android:maxLines="2"
android:textColor="@color/gradientTitleColor"
android:textSize="20sp" />
</RelativeLayout>
</androidx.cardview.widget.CardView>

View file

@ -1,37 +1,42 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="96dp"
android:paddingTop="4dp"
android:paddingEnd="4dp">
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/card_view"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/playlist_card_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:backgroundTint="@color/cardColor"
card_view:cardCornerRadius="4dp"
card_view:cardElevation="2dp"
card_view:cardPreventCornerOverlap="false"
card_view:cardUseCompatPadding="true">
app:cardCornerRadius="4dp">
<ImageView
android:id="@+id/playlist_cover_image_view"
android:layout_width="match_parent"
android:layout_height="196dp" />
<View
android:layout_width="match_parent"
android:layout_height="196dp"
android:background="@drawable/gradient_discover_background_image" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="196dp">
<TextView
android:id="@+id/playlist_name_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:padding="18dp"
android:fontFamily="@font/open_sans_font_family"
android:text="@string/label_placeholder"
android:textColor="@color/titleTextColor"
android:textSize="16sp"
android:textStyle="bold"
android:textAlignment="center"/>
android:layout_marginLeft="16dp"
android:layout_marginTop="18dp"
android:layout_marginRight="16dp"
android:ellipsize="end"
android:maxLines="2"
android:textColor="@color/gradientTitleColor"
android:textSize="20sp" />
</RelativeLayout>
</androidx.cardview.widget.CardView>
</RelativeLayout>

View file

@ -89,6 +89,9 @@
<action
android:id="@+id/action_libraryFragment_to_genreCatalogueFragment"
app:destination="@id/genreCatalogueFragment" />
<action
android:id="@+id/action_libraryFragment_to_playlistCatalogueFragment"
app:destination="@id/playlistCatalogueFragment" />
<action
android:id="@+id/action_libraryFragment_to_artistPageFragment"
app:destination="@id/artistPageFragment"/>
@ -172,6 +175,15 @@
android:id="@+id/action_genreCatalogueFragment_to_filterFragment"
app:destination="@id/filterFragment" />
</fragment>
<fragment
android:id="@+id/playlistCatalogueFragment"
android:name="com.cappielloantonio.play.ui.fragment.PlaylistCatalogueFragment"
android:label="PlaylistCatalogueFragment"
tools:layout="@layout/fragment_playlist_catalogue">
<action
android:id="@+id/action_playlistCatalogueFragment_to_playlistPageFragment"
app:destination="@id/playlistPageFragment" />
</fragment>
<fragment
android:id="@+id/artistPageFragment"