Added a viewpager on the music playback screen to display the lyrics

This commit is contained in:
CappielloAntonio 2021-12-20 18:15:09 +01:00
parent 84fc07efe7
commit d8662820f7
15 changed files with 176 additions and 162 deletions

4
.idea/misc.xml generated
View file

@ -77,6 +77,8 @@
<entry key="app/src/main/res/layout/fragment_library.xml" value="0.1" />
<entry key="app/src/main/res/layout/fragment_login.xml" value="0.3166496424923391" />
<entry key="app/src/main/res/layout/fragment_player_bottom_sheet.xml" value="0.3166496424923391" />
<entry key="app/src/main/res/layout/fragment_player_cover.xml" value="0.528125" />
<entry key="app/src/main/res/layout/fragment_player_lyrics.xml" value="0.528125" />
<entry key="app/src/main/res/layout/fragment_playlist_catalogue.xml" value="0.3229166666666667" />
<entry key="app/src/main/res/layout/fragment_playlist_page.xml" value="0.3229166666666667" />
<entry key="app/src/main/res/layout/fragment_settings.xml" value="0.3229166666666667" />
@ -112,7 +114,7 @@
<entry key="app/src/main/res/layout/item_placeholder_year.xml" value="0.3229166666666667" />
<entry key="app/src/main/res/layout/item_player_now_playing_song.xml" value="0.3229166666666667" />
<entry key="app/src/main/res/layout/item_player_queue_song.xml" value="0.1" />
<entry key="app/src/main/res/layout/player_body_bottom_sheet.xml" value="0.528125" />
<entry key="app/src/main/res/layout/player_body_bottom_sheet.xml" value="0.1" />
<entry key="app/src/main/res/layout/player_body_new_bottom_sheet.xml" value="0.528125" />
<entry key="app/src/main/res/layout/player_header_bottom_sheet.xml" value="0.3229166666666667" />
<entry key="app/src/main/res/menu/bottom_nav_menu.xml" value="0.3229166666666667" />

View file

@ -66,6 +66,7 @@ dependencies {
// Exoplayer
implementation 'com.google.android.exoplayer:exoplayer:2.12.2'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
annotationProcessor 'androidx.room:room-compiler:2.4.0'

View file

@ -1,91 +1,34 @@
package com.cappielloantonio.play.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import androidx.fragment.app.Fragment;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.cappielloantonio.play.R;
import com.cappielloantonio.play.glide.CustomGlideRequest;
import com.cappielloantonio.play.model.Song;
import com.cappielloantonio.play.service.MusicPlayerRemote;
import com.cappielloantonio.play.ui.fragment.PlayerCoverFragment;
import com.cappielloantonio.play.ui.fragment.PlayerLyricsFragment;
import java.util.ArrayList;
import java.util.List;
public class PlayerNowPlayingSongAdapter extends FragmentStateAdapter {
private static final String TAG = "PlayerNowPlayingSongInfoAdapter";
public class PlayerNowPlayingSongAdapter extends RecyclerView.Adapter<PlayerNowPlayingSongAdapter.ViewHolder> {
private static final String TAG = "PlayerNowPlayingSongAdapter";
private final LayoutInflater inflater;
private final Context context;
private List<Song> songs;
public PlayerNowPlayingSongAdapter(Context context) {
this.context = context;
this.inflater = LayoutInflater.from(context);
this.songs = new ArrayList<>();
public PlayerNowPlayingSongAdapter(@NonNull Fragment fragment) {
super(fragment);
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = inflater.inflate(R.layout.item_player_now_playing_song, parent, false);
return new ViewHolder(view);
public Fragment createFragment(int position) {
switch (position) {
case 0:
return new PlayerCoverFragment();
case 1:
return new PlayerLyricsFragment();
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Song song = songs.get(position);
CustomGlideRequest.Builder
.from(context, song.getId(), CustomGlideRequest.SONG_PIC, null)
.build()
.transform(new RoundedCorners(CustomGlideRequest.CORNER_RADIUS))
.into(holder.cover);
return new PlayerCoverFragment();
}
@Override
public int getItemCount() {
return songs.size();
}
public Song getItem(int position) {
try {
return songs.get(position);
} catch (IndexOutOfBoundsException e) {
return null;
}
}
public void setItems(List<Song> songs) {
this.songs = songs;
notifyDataSetChanged();
}
public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
ImageView cover;
ViewHolder(View itemView) {
super(itemView);
cover = itemView.findViewById(R.id.now_playing_song_cover_image_view);
itemView.setOnClickListener(this);
}
@Override
public void onClick(View view) {
if (MusicPlayerRemote.isPlaying()) {
MusicPlayerRemote.pauseSong();
} else {
MusicPlayerRemote.resumePlaying();
}
}
return 2;
}
}

View file

@ -101,7 +101,7 @@ public class PlayerSongQueueAdapter extends RecyclerView.Adapter<PlayerSongQueue
@Override
public void onClick(View view) {
playerBottomSheetFragment.scrollPager(songs.get(getBindingAdapterPosition()), getBindingAdapterPosition(), false);
playerBottomSheetFragment.setSongInfo(songs.get(getBindingAdapterPosition()));
MusicPlayerRemote.openQueue(songs, getBindingAdapterPosition(), true);
}
}

View file

@ -188,7 +188,7 @@ public class MainActivity extends BaseActivity {
PlayerBottomSheetFragment playerBottomSheetFragment = (PlayerBottomSheetFragment) getSupportFragmentManager().findFragmentByTag("PlayerBottomSheet");
if (playerBottomSheetFragment == null) return;
playerBottomSheetFragment.scrollPager(song, 0, false);
playerBottomSheetFragment.setSongInfo(song);
}
public void collapseBottomSheet() {

View file

@ -1,8 +1,10 @@
package com.cappielloantonio.play.ui.fragment;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ScrollView;
@ -34,7 +36,6 @@ import com.cappielloantonio.play.ui.activity.MainActivity;
import com.cappielloantonio.play.ui.dialog.RatingDialog;
import com.cappielloantonio.play.util.MappingUtil;
import com.cappielloantonio.play.util.MusicUtil;
import com.cappielloantonio.play.util.PreferenceUtil;
import com.cappielloantonio.play.viewmodel.PlayerBottomSheetViewModel;
import java.util.Collections;
@ -49,7 +50,6 @@ public class PlayerBottomSheetFragment extends Fragment implements MusicServiceE
private MainActivity activity;
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
private PlayerNowPlayingSongAdapter playerNowPlayingSongAdapter;
private PlayerSongQueueAdapter playerSongQueueAdapter;
private MusicProgressViewUpdateHelper progressViewUpdateHelper;
@ -75,8 +75,7 @@ public class PlayerBottomSheetFragment extends Fragment implements MusicServiceE
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
init();
initLyricsView();
initQueueSlideView();
initCoverLyricsSlideView();
initQueueRecyclerView();
initFavoriteButtonClick();
initMusicCommandUnfoldButton();
@ -127,59 +126,9 @@ public class PlayerBottomSheetFragment extends Fragment implements MusicServiceE
bodyBind.playerMoveDownBottomSheet.setOnClickListener(view -> activity.collapseBottomSheet());
}
private void initLyricsView() {
/*playerBottomSheetViewModel.getLyrics().observe(requireActivity(), lyrics -> {
if (lyrics != null && !lyrics.trim().equals("")) {
bodyBind.playerSongLyricsCardview.setVisibility(View.VISIBLE);
} else {
bodyBind.playerSongLyricsCardview.setVisibility(View.GONE);
}
bodyBind.playerSongLyricsTextView.setText(MusicUtil.getReadableString(lyrics));
});
bodyBind.playerSongLyricsLabelClickable.setOnClickListener(view -> {
if (bodyBind.playerSongLyricsTextView.getVisibility() == View.INVISIBLE || bodyBind.playerSongLyricsTextView.getVisibility() == View.GONE) {
setLyricsTextViewVisibility(true);
} else {
setLyricsTextViewVisibility(false);
}
});*/
}
private void initQueueSlideView() {
private void initCoverLyricsSlideView() {
bodyBind.playerSongCoverViewPager.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL);
playerNowPlayingSongAdapter = new PlayerNowPlayingSongAdapter(requireContext());
bodyBind.playerSongCoverViewPager.setAdapter(playerNowPlayingSongAdapter);
playerBottomSheetViewModel.getQueueSong().observe(requireActivity(), queue -> playerNowPlayingSongAdapter.setItems(MappingUtil.mapQueue(queue)));
bodyBind.playerSongCoverViewPager.setOffscreenPageLimit(3);
bodyBind.playerSongCoverViewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
// 0 = IDLE
// 1 = DRAGGING
// 2 = SETTLING
// -1 = NEW
int pageState = -1;
@Override
public void onPageScrollStateChanged(int state) {
super.onPageScrollStateChanged(state);
pageState = state;
}
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
if (pageState != -1) {
MusicPlayerRemote.playSongAt(position);
pageState = -1;
}
}
});
setViewPageDelayed(PreferenceUtil.getInstance(requireContext()).getPosition());
bodyBind.playerSongCoverViewPager.setAdapter(new PlayerNowPlayingSongAdapter(this));
}
private void initQueueRecyclerView() {
@ -326,17 +275,8 @@ public class PlayerBottomSheetFragment extends Fragment implements MusicServiceE
});
}
private void setViewPageDelayed(int position) {
final Handler handler = new Handler();
final Runnable r = () -> {
if (bind != null) bodyBind.playerSongCoverViewPager.setCurrentItem(position, false);
};
handler.postDelayed(r, 100);
}
private void setSongInfo(Song song) {
// setLyricsTextViewVisibility(false);
playerBottomSheetViewModel.refreshSongLyrics(requireActivity(), song);
public void setSongInfo(Song song) {
playerBottomSheetViewModel.refreshSongInfo(requireActivity(), song);
bodyBind.playerSongTitleLabel.setText(MusicUtil.getReadableString(song.getTitle()));
bodyBind.playerArtistNameLabel.setText(MusicUtil.getReadableString(song.getArtistName()));
@ -354,7 +294,7 @@ public class PlayerBottomSheetFragment extends Fragment implements MusicServiceE
}
public void setPlayerCommandViewVisibility(boolean isVisible) {
if(isVisible) {
if (isVisible) {
bodyBind.playerCommandCardview.setVisibility(View.VISIBLE);
} else {
bodyBind.playerCommandCardview.setVisibility(View.GONE);
@ -377,11 +317,6 @@ public class PlayerBottomSheetFragment extends Fragment implements MusicServiceE
bind.playerNestedScrollView.fullScroll(ScrollView.FOCUS_UP);
}
public void scrollPager(Song song, int page, boolean smoothScroll) {
bodyBind.playerSongCoverViewPager.setCurrentItem(page, smoothScroll);
setSongInfo(song);
}
@Override
public void onServiceConnected() {
setSongInfo(Objects.requireNonNull(MusicPlayerRemote.getCurrentSong()));
@ -400,7 +335,6 @@ public class PlayerBottomSheetFragment extends Fragment implements MusicServiceE
@Override
public void onPlayMetadataChanged() {
setViewPageDelayed(MusicPlayerRemote.getPosition());
setSongInfo(Objects.requireNonNull(MusicPlayerRemote.getCurrentSong()));
}

View file

@ -0,0 +1,51 @@
package com.cappielloantonio.play.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.cappielloantonio.play.databinding.FragmentPlayerCoverBinding;
import com.cappielloantonio.play.glide.CustomGlideRequest;
import com.cappielloantonio.play.viewmodel.PlayerBottomSheetViewModel;
public class PlayerCoverFragment extends Fragment {
private static final String TAG = "PlayerCoverFragment";
private FragmentPlayerCoverBinding bind;
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
bind = FragmentPlayerCoverBinding.inflate(inflater, container, false);
View view = bind.getRoot();
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
init();
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void init() {
playerBottomSheetViewModel.getLiveSong().observe(requireActivity(), song -> {
if (song != null) {
CustomGlideRequest.Builder
.from(requireContext(), song.getId(), CustomGlideRequest.SONG_PIC, null)
.build()
.transform(new RoundedCorners(CustomGlideRequest.CORNER_RADIUS))
.into(bind.nowPlayingSongCoverImageView);
}
});
}
}

View file

@ -0,0 +1,52 @@
package com.cappielloantonio.play.ui.fragment;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.text.method.ScrollingMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import com.cappielloantonio.play.R;
import com.cappielloantonio.play.databinding.FragmentPlayerLyricsBinding;
import com.cappielloantonio.play.util.MusicUtil;
import com.cappielloantonio.play.viewmodel.PlayerBottomSheetViewModel;
public class PlayerLyricsFragment extends Fragment {
private static final String TAG = "PlayerLyricsFragment";
private FragmentPlayerLyricsBinding bind;
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
bind = FragmentPlayerLyricsBinding.inflate(inflater, container, false);
View view = bind.getRoot();
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
initLyrics();
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void initLyrics() {
playerBottomSheetViewModel.getLiveLyrics().observe(requireActivity(), lyrics -> {
if (lyrics == null || lyrics.trim().equals("")) {
bind.nowPlayingSongLyricsTextView.setText(R.string.player_song_lyrics_none_available_label);
} else {
bind.nowPlayingSongLyricsTextView.setText(MusicUtil.getReadableString(lyrics));
}
});
}
}

View file

@ -29,7 +29,9 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
private final ArtistRepository artistRepository;
private final QueueRepository queueRepository;
private final MutableLiveData<String> songLyrics = new MutableLiveData<>(null);
private final MutableLiveData<String> lyricsLiveData = new MutableLiveData<>(null);
private final MutableLiveData<Song> songLiveData = new MutableLiveData<>(null);
private final LiveData<List<Queue>> queueSong;
public PlayerBottomSheetViewModel(@NonNull Application application) {
@ -81,11 +83,17 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
return artistRepository.getArtist(song.getArtistId());
}
public LiveData<String> getLyrics() {
return songLyrics;
public LiveData<Song> getLiveSong() {
return songLiveData;
}
public void refreshSongLyrics(LifecycleOwner owner, Song song) {
songRepository.getSongLyrics(song).observe(owner, songLyrics::postValue);
public LiveData<String> getLiveLyrics() {
return lyricsLiveData;
}
public void refreshSongInfo(LifecycleOwner owner, Song song) {
songLiveData.postValue(song);
songRepository.getSongLyrics(song).observe(owner, lyricsLiveData::postValue);
}
}

View file

@ -0,0 +1,6 @@
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/now_playing_song_cover_image_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="24dp"
android:paddingEnd="24dp"/>

View file

@ -0,0 +1,12 @@
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/now_playing_song_lyrics_text_view"
style="@style/LyricsTextView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="24dp"
android:paddingEnd="24dp"/>
</androidx.core.widget.NestedScrollView>

View file

@ -2,6 +2,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/player_body_bottom_sheet_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
@ -53,9 +54,6 @@
android:id="@+id/player_song_cover_view_pager"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="24dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="24dp"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
@ -140,7 +138,7 @@
<TextView
android:id="@+id/player_artist_name_label"
style="@style/SubheadTextView"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="24dp"
android:paddingEnd="24dp"

View file

@ -19,7 +19,7 @@
<color name="suggestionIconColor">#DADADA</color>
<color name="darkIconColor">#CFCFCF</color>
<color name="suggestionSelectedTextColor">#CCCCCC</color>
<color name="dividerColor">#080808</color>
<color name="dividerColor">#404040</color>
<color name="hintTextColor">#CFCFCF</color>
<color name="buttonBackgroundColor">#1D1D1D</color>

View file

@ -114,6 +114,7 @@
<string name="player_hide_lyrics_button">Hide</string>
<string name="player_show_lyrics_button">Show</string>
<string name="player_song_lyrics_label">Lyrics</string>
<string name="player_song_lyrics_none_available_label">No lyrics available</string>
<string name="playing_notification_description">The playing notification provides actions for play/pause etc.</string>
<string name="playing_notification_name">Playing Notification</string>
<string name="playlist_catalogue_title">Playlist Catalogue</string>

View file

@ -129,6 +129,12 @@
<item name="android:textColor">@color/titleTextColor</item>
</style>
<style name="LyricsTextView" parent="InterFontFamily">
<item name="android:textSize">24sp</item>
<item name="android:textFontWeight">700</item>
<item name="android:textColor">@color/titleTextColor</item>
</style>
<!-- Font dei pulsanti vari presenti nell'applicazione -->
<style name="MainButton" parent="Widget.MaterialComponents.Button.UnelevatedButton">
<item name="android:fontFamily">@font/inter</item>