mirror of
https://github.com/antebudimir/tempus.git
synced 2025-12-31 09:33:33 +00:00
Start of work for a structural modification of the player
This commit is contained in:
parent
9fdc9ff44d
commit
0fbf3a4cdf
18 changed files with 900 additions and 484 deletions
13
.idea/misc.xml
generated
13
.idea/misc.xml
generated
|
|
@ -79,11 +79,17 @@
|
||||||
<entry key="app/src/main/res/layout/fragment_login.xml" value="0.3166496424923391" />
|
<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_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_cover.xml" value="0.528125" />
|
||||||
|
<entry key="app/src/main/res/layout/fragment_player_info.xml" value="0.3229166666666667" />
|
||||||
<entry key="app/src/main/res/layout/fragment_player_lyrics.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_player_queue.xml" value="0.3229166666666667" />
|
||||||
<entry key="app/src/main/res/layout/fragment_playlist_catalogue.xml" value="0.3229166666666667" />
|
<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_playlist_page.xml" value="0.3229166666666667" />
|
||||||
<entry key="app/src/main/res/layout/fragment_settings.xml" value="0.3229166666666667" />
|
<entry key="app/src/main/res/layout/fragment_settings.xml" value="0.3229166666666667" />
|
||||||
<entry key="app/src/main/res/layout/fragment_song_list_page.xml" value="0.225" />
|
<entry key="app/src/main/res/layout/fragment_song_list_page.xml" value="0.225" />
|
||||||
|
<entry key="app/src/main/res/layout/inner_fragment_player_controller.xml" value="0.3229166666666667" />
|
||||||
|
<entry key="app/src/main/res/layout/inner_fragment_player_cover.xml" value="0.3229166666666667" />
|
||||||
|
<entry key="app/src/main/res/layout/inner_fragment_player_lyrics.xml" value="0.3229166666666667" />
|
||||||
|
<entry key="app/src/main/res/layout/inner_fragment_player_queue.xml" value="0.3229166666666667" />
|
||||||
<entry key="app/src/main/res/layout/item_home_album_placeholder.xml" value="0.3229166666666667" />
|
<entry key="app/src/main/res/layout/item_home_album_placeholder.xml" value="0.3229166666666667" />
|
||||||
<entry key="app/src/main/res/layout/item_home_discover_song.xml" value="0.3166496424923391" />
|
<entry key="app/src/main/res/layout/item_home_discover_song.xml" value="0.3166496424923391" />
|
||||||
<entry key="app/src/main/res/layout/item_home_discovery_placeholder.xml" value="0.3229166666666667" />
|
<entry key="app/src/main/res/layout/item_home_discovery_placeholder.xml" value="0.3229166666666667" />
|
||||||
|
|
@ -115,9 +121,12 @@
|
||||||
<entry key="app/src/main/res/layout/item_placeholder_year.xml" value="0.3229166666666667" />
|
<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_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/item_player_queue_song.xml" value="0.1" />
|
||||||
<entry key="app/src/main/res/layout/player_body_bottom_sheet.xml" value="0.7184241019698725" />
|
<entry key="app/src/main/res/layout/player_body_bottom_sheet.xml" value="0.3229166666666667" />
|
||||||
<entry key="app/src/main/res/layout/player_body_new_bottom_sheet.xml" value="0.528125" />
|
<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/layout/player_control_view.xml" value="0.3229166666666667" />
|
||||||
|
<entry key="app/src/main/res/layout/player_control_view_body.xml" value="0.3229166666666667" />
|
||||||
|
<entry key="app/src/main/res/layout/player_control_view_header.xml" value="0.3229166666666667" />
|
||||||
|
<entry key="app/src/main/res/layout/player_header_bottom_sheet.xml" value="0.1" />
|
||||||
<entry key="app/src/main/res/menu/bottom_nav_menu.xml" value="0.3229166666666667" />
|
<entry key="app/src/main/res/menu/bottom_nav_menu.xml" value="0.3229166666666667" />
|
||||||
<entry key="app/src/main/res/menu/login_page_menu.xml" value="0.3229166666666667" />
|
<entry key="app/src/main/res/menu/login_page_menu.xml" value="0.3229166666666667" />
|
||||||
<entry key="app/src/main/res/menu/main_page_menu.xml" value="0.3229166666666667" />
|
<entry key="app/src/main/res/menu/main_page_menu.xml" value="0.3229166666666667" />
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.cappielloantonio.play.adapter;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||||
|
|
||||||
|
import com.cappielloantonio.play.ui.fragment.PlayerControllerFragment;
|
||||||
|
import com.cappielloantonio.play.ui.fragment.PlayerQueueFragment;
|
||||||
|
|
||||||
|
public class PlayerBodyAdapter extends FragmentStateAdapter {
|
||||||
|
private static final String TAG = "PlayerNowPlayingSongInfoAdapter";
|
||||||
|
|
||||||
|
public PlayerBodyAdapter(@NonNull Fragment fragment) {
|
||||||
|
super(fragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Fragment createFragment(int position) {
|
||||||
|
switch (position) {
|
||||||
|
case 0:
|
||||||
|
return new PlayerControllerFragment();
|
||||||
|
case 1:
|
||||||
|
return new PlayerQueueFragment();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PlayerControllerFragment();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -27,15 +27,13 @@ public class PlayerSongQueueAdapter extends RecyclerView.Adapter<PlayerSongQueue
|
||||||
private static final String TAG = "SongResultSearchAdapter";
|
private static final String TAG = "SongResultSearchAdapter";
|
||||||
|
|
||||||
private final LayoutInflater mInflater;
|
private final LayoutInflater mInflater;
|
||||||
private final PlayerBottomSheetFragment playerBottomSheetFragment;
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
|
||||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||||
private List<Song> songs;
|
private List<Song> songs;
|
||||||
|
|
||||||
public PlayerSongQueueAdapter(Context context, PlayerBottomSheetFragment playerBottomSheetFragment) {
|
public PlayerSongQueueAdapter(Context context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.playerBottomSheetFragment = playerBottomSheetFragment;
|
|
||||||
this.mInflater = LayoutInflater.from(context);
|
this.mInflater = LayoutInflater.from(context);
|
||||||
this.songs = new ArrayList<>();
|
this.songs = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
package com.cappielloantonio.play.helper.recyclerview
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewConfiguration
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
|
import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
import kotlin.math.sign
|
||||||
|
|
||||||
|
class NestedScrollableHost : FrameLayout {
|
||||||
|
constructor(context: Context) : super(context)
|
||||||
|
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||||
|
|
||||||
|
private var touchSlop = 0
|
||||||
|
private var initialX = 0f
|
||||||
|
private var initialY = 0f
|
||||||
|
private val parentViewPager: ViewPager2?
|
||||||
|
get() {
|
||||||
|
var v: View? = parent as? View
|
||||||
|
while (v != null && v !is ViewPager2) {
|
||||||
|
v = v.parent as? View
|
||||||
|
}
|
||||||
|
return v as? ViewPager2
|
||||||
|
}
|
||||||
|
|
||||||
|
private val child: View? get() = if (childCount > 0) getChildAt(0) else null
|
||||||
|
|
||||||
|
init {
|
||||||
|
touchSlop = ViewConfiguration.get(context).scaledTouchSlop
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun canChildScroll(orientation: Int, delta: Float): Boolean {
|
||||||
|
val direction = -delta.sign.toInt()
|
||||||
|
return when (orientation) {
|
||||||
|
0 -> child?.canScrollHorizontally(direction) ?: false
|
||||||
|
1 -> child?.canScrollVertically(direction) ?: false
|
||||||
|
else -> throw IllegalArgumentException()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onInterceptTouchEvent(e: MotionEvent): Boolean {
|
||||||
|
handleInterceptTouchEvent(e)
|
||||||
|
return super.onInterceptTouchEvent(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleInterceptTouchEvent(e: MotionEvent) {
|
||||||
|
val orientation = parentViewPager?.orientation ?: return
|
||||||
|
|
||||||
|
// Early return if child can't scroll in same direction as parent
|
||||||
|
if (!canChildScroll(orientation, -1f) && !canChildScroll(orientation, 1f)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.action == MotionEvent.ACTION_DOWN) {
|
||||||
|
initialX = e.x
|
||||||
|
initialY = e.y
|
||||||
|
parent.requestDisallowInterceptTouchEvent(true)
|
||||||
|
} else if (e.action == MotionEvent.ACTION_MOVE) {
|
||||||
|
val dx = e.x - initialX
|
||||||
|
val dy = e.y - initialY
|
||||||
|
val isVpHorizontal = orientation == ORIENTATION_HORIZONTAL
|
||||||
|
|
||||||
|
// assuming ViewPager2 touch-slop is 2x touch-slop of child
|
||||||
|
val scaledDx = dx.absoluteValue * if (isVpHorizontal) .5f else 1f
|
||||||
|
val scaledDy = dy.absoluteValue * if (isVpHorizontal) 1f else .5f
|
||||||
|
|
||||||
|
if (scaledDx > touchSlop || scaledDy > touchSlop) {
|
||||||
|
if (isVpHorizontal == (scaledDy > scaledDx)) {
|
||||||
|
// Gesture is perpendicular, allow all parents to intercept
|
||||||
|
parent.requestDisallowInterceptTouchEvent(false)
|
||||||
|
} else {
|
||||||
|
// Gesture is parallel, query child if movement in that direction is possible
|
||||||
|
if (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) {
|
||||||
|
// Child can scroll, disallow all parents to intercept
|
||||||
|
parent.requestDisallowInterceptTouchEvent(true)
|
||||||
|
} else {
|
||||||
|
// Child cannot scroll, allow all parents to intercept
|
||||||
|
parent.requestDisallowInterceptTouchEvent(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -152,18 +152,18 @@ public class MainActivity extends BaseActivity {
|
||||||
break;
|
break;
|
||||||
case BottomSheetBehavior.STATE_COLLAPSED:
|
case BottomSheetBehavior.STATE_COLLAPSED:
|
||||||
if (playerBottomSheetFragment != null) {
|
if (playerBottomSheetFragment != null) {
|
||||||
playerBottomSheetFragment.goBackToFirstPage();
|
// playerBottomSheetFragment.goBackToFirstPage();
|
||||||
playerBottomSheetFragment.scrollOnTop();
|
// playerBottomSheetFragment.scrollOnTop();
|
||||||
}
|
}
|
||||||
case BottomSheetBehavior.STATE_SETTLING:
|
case BottomSheetBehavior.STATE_SETTLING:
|
||||||
if (playerBottomSheetFragment != null) {
|
if (playerBottomSheetFragment != null) {
|
||||||
playerBottomSheetFragment.scrollOnTop();
|
// playerBottomSheetFragment.scrollOnTop();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case BottomSheetBehavior.STATE_EXPANDED:
|
case BottomSheetBehavior.STATE_EXPANDED:
|
||||||
if (playerBottomSheetFragment != null) {
|
if (playerBottomSheetFragment != null) {
|
||||||
playerBottomSheetFragment.scrollOnTop();
|
// playerBottomSheetFragment.scrollOnTop();
|
||||||
setBottomSheetDraggableState(playerBottomSheetFragment.isViewPagerInFirstPage());
|
// setBottomSheetDraggableState(playerBottomSheetFragment.isViewPagerInFirstPage());
|
||||||
}
|
}
|
||||||
case BottomSheetBehavior.STATE_DRAGGING:
|
case BottomSheetBehavior.STATE_DRAGGING:
|
||||||
case BottomSheetBehavior.STATE_HALF_EXPANDED:
|
case BottomSheetBehavior.STATE_HALF_EXPANDED:
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import androidx.media3.common.Player;
|
||||||
import androidx.media3.session.MediaBrowser;
|
import androidx.media3.session.MediaBrowser;
|
||||||
import androidx.media3.session.MediaController;
|
import androidx.media3.session.MediaController;
|
||||||
import androidx.media3.session.SessionToken;
|
import androidx.media3.session.SessionToken;
|
||||||
|
import androidx.media3.ui.PlayerControlView;
|
||||||
import androidx.navigation.fragment.NavHostFragment;
|
import androidx.navigation.fragment.NavHostFragment;
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
|
@ -31,6 +32,7 @@ import androidx.viewpager2.widget.ViewPager2;
|
||||||
|
|
||||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
|
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
|
||||||
import com.cappielloantonio.play.R;
|
import com.cappielloantonio.play.R;
|
||||||
|
import com.cappielloantonio.play.adapter.PlayerBodyAdapter;
|
||||||
import com.cappielloantonio.play.adapter.PlayerNowPlayingSongAdapter;
|
import com.cappielloantonio.play.adapter.PlayerNowPlayingSongAdapter;
|
||||||
import com.cappielloantonio.play.adapter.PlayerSongQueueAdapter;
|
import com.cappielloantonio.play.adapter.PlayerSongQueueAdapter;
|
||||||
import com.cappielloantonio.play.databinding.FragmentPlayerBottomSheetBinding;
|
import com.cappielloantonio.play.databinding.FragmentPlayerBottomSheetBinding;
|
||||||
|
|
@ -51,20 +53,11 @@ public class PlayerBottomSheetFragment extends Fragment {
|
||||||
private static final String TAG = "PlayerBottomSheetFragment";
|
private static final String TAG = "PlayerBottomSheetFragment";
|
||||||
|
|
||||||
private FragmentPlayerBottomSheetBinding bind;
|
private FragmentPlayerBottomSheetBinding bind;
|
||||||
private ImageView playerMoveDownBottomSheet;
|
|
||||||
private ViewPager2 playerSongCoverViewPager;
|
|
||||||
private RecyclerView playerQueueRecyclerView;
|
|
||||||
private ToggleButton buttonFavorite;
|
|
||||||
private ImageButton playerCommandUnfoldButton;
|
|
||||||
private CardView playerCommandCardview;
|
|
||||||
private TextView playerSongTitleLabel;
|
|
||||||
private TextView playerArtistNameLabel;
|
|
||||||
|
|
||||||
private MainActivity activity;
|
|
||||||
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
|
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
|
||||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||||
|
|
||||||
private PlayerSongQueueAdapter playerSongQueueAdapter;
|
// TODO: Collegare la seekbar all'exo_progress
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
|
@ -74,19 +67,12 @@ public class PlayerBottomSheetFragment extends Fragment {
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
activity = (MainActivity) getActivity();
|
|
||||||
|
|
||||||
bind = FragmentPlayerBottomSheetBinding.inflate(inflater, container, false);
|
bind = FragmentPlayerBottomSheetBinding.inflate(inflater, container, false);
|
||||||
View view = bind.getRoot();
|
View view = bind.getRoot();
|
||||||
|
|
||||||
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
|
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
|
||||||
|
|
||||||
init();
|
initViewPager();
|
||||||
initCoverLyricsSlideView();
|
|
||||||
initQueueRecyclerView();
|
|
||||||
initMediaListenable();
|
|
||||||
initMusicCommandUnfoldButton();
|
|
||||||
initArtistLabelButton();
|
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
@ -97,7 +83,6 @@ public class PlayerBottomSheetFragment extends Fragment {
|
||||||
|
|
||||||
initializeMediaBrowser();
|
initializeMediaBrowser();
|
||||||
bindMediaController();
|
bindMediaController();
|
||||||
setMediaBrowserListenableFuture();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -112,19 +97,9 @@ public class PlayerBottomSheetFragment extends Fragment {
|
||||||
bind = null;
|
bind = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("UnsafeOptInUsageError")
|
private void initViewPager() {
|
||||||
private void init() {
|
bind.playerBodyLayout.playerBodyBottomSheetViewPager.setOrientation(ViewPager2.ORIENTATION_VERTICAL);
|
||||||
playerMoveDownBottomSheet = bind.getRoot().findViewById(R.id.player_move_down_bottom_sheet);
|
bind.playerBodyLayout.playerBodyBottomSheetViewPager.setAdapter(new PlayerBodyAdapter(this));
|
||||||
playerSongCoverViewPager = bind.getRoot().findViewById(R.id.player_song_cover_view_pager);
|
|
||||||
playerQueueRecyclerView = bind.getRoot().findViewById(R.id.player_queue_recycler_view);
|
|
||||||
buttonFavorite = bind.getRoot().findViewById(R.id.button_favorite);
|
|
||||||
playerCommandUnfoldButton = bind.getRoot().findViewById(R.id.player_command_unfold_button);
|
|
||||||
playerCommandCardview = bind.getRoot().findViewById(R.id.player_command_cardview);
|
|
||||||
playerSongTitleLabel = bind.getRoot().findViewById(R.id.player_song_title_label);
|
|
||||||
playerArtistNameLabel = bind.getRoot().findViewById(R.id.player_artist_name_label);
|
|
||||||
|
|
||||||
playerMoveDownBottomSheet.setOnClickListener(view -> activity.collapseBottomSheet());
|
|
||||||
bind.playerBodyLayout.setProgressUpdateListener((position, bufferedPosition) -> bind.playerHeaderLayout.playerHeaderSeekBar.setProgress((int) (position / 1000), true));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("UnsafeOptInUsageError")
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
|
|
@ -136,18 +111,12 @@ public class PlayerBottomSheetFragment extends Fragment {
|
||||||
MediaController.releaseFuture(mediaBrowserListenableFuture);
|
MediaController.releaseFuture(mediaBrowserListenableFuture);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setMediaBrowserListenableFuture() {
|
|
||||||
playerSongQueueAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("UnsafeOptInUsageError")
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
private void bindMediaController() {
|
private void bindMediaController() {
|
||||||
mediaBrowserListenableFuture.addListener(() -> {
|
mediaBrowserListenableFuture.addListener(() -> {
|
||||||
try {
|
try {
|
||||||
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
|
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
|
||||||
|
|
||||||
bind.playerBodyLayout.setPlayer(mediaBrowser);
|
|
||||||
|
|
||||||
setMediaControllerListener(mediaBrowser);
|
setMediaControllerListener(mediaBrowser);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, e.getMessage());
|
Log.e(TAG, e.getMessage());
|
||||||
|
|
@ -190,9 +159,6 @@ public class PlayerBottomSheetFragment extends Fragment {
|
||||||
bind.playerHeaderLayout.playerHeaderSongTitleLabel.setText(MusicUtil.getReadableString(String.valueOf(mediaMetadata.title)));
|
bind.playerHeaderLayout.playerHeaderSongTitleLabel.setText(MusicUtil.getReadableString(String.valueOf(mediaMetadata.title)));
|
||||||
bind.playerHeaderLayout.playerHeaderSongArtistLabel.setText(MusicUtil.getReadableString(String.valueOf(mediaMetadata.artist)));
|
bind.playerHeaderLayout.playerHeaderSongArtistLabel.setText(MusicUtil.getReadableString(String.valueOf(mediaMetadata.artist)));
|
||||||
|
|
||||||
playerSongTitleLabel.setText(MusicUtil.getReadableString(String.valueOf(mediaMetadata.title)));
|
|
||||||
playerArtistNameLabel.setText(MusicUtil.getReadableString(String.valueOf(mediaMetadata.artist)));
|
|
||||||
|
|
||||||
if (mediaMetadata.extras != null) CustomGlideRequest.Builder
|
if (mediaMetadata.extras != null) CustomGlideRequest.Builder
|
||||||
.from(requireContext(), mediaMetadata.extras.getString("id"), CustomGlideRequest.SONG_PIC, null)
|
.from(requireContext(), mediaMetadata.extras.getString("id"), CustomGlideRequest.SONG_PIC, null)
|
||||||
.build()
|
.build()
|
||||||
|
|
@ -226,143 +192,7 @@ public class PlayerBottomSheetFragment extends Fragment {
|
||||||
bind.playerHeaderLayout.playerHeaderNextSongButton.setAlpha(isEnabled ? (float) 1.0 : (float) 0.3);
|
bind.playerHeaderLayout.playerHeaderNextSongButton.setAlpha(isEnabled ? (float) 1.0 : (float) 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initCoverLyricsSlideView() {
|
|
||||||
playerSongCoverViewPager.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL);
|
|
||||||
playerSongCoverViewPager.setAdapter(new PlayerNowPlayingSongAdapter(this));
|
|
||||||
|
|
||||||
playerSongCoverViewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
|
|
||||||
@Override
|
|
||||||
public void onPageSelected(int position) {
|
|
||||||
super.onPageSelected(position);
|
|
||||||
|
|
||||||
if (position == 0) {
|
|
||||||
activity.setBottomSheetDraggableState(true);
|
|
||||||
} else if (position == 1) {
|
|
||||||
activity.setBottomSheetDraggableState(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initQueueRecyclerView() {
|
|
||||||
playerQueueRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
|
||||||
playerQueueRecyclerView.setHasFixedSize(true);
|
|
||||||
|
|
||||||
playerSongQueueAdapter = new PlayerSongQueueAdapter(requireContext(), this);
|
|
||||||
playerQueueRecyclerView.setAdapter(playerSongQueueAdapter);
|
|
||||||
playerBottomSheetViewModel.getQueueSong().observe(requireActivity(), queue -> {
|
|
||||||
if (queue != null) {
|
|
||||||
playerSongQueueAdapter.setItems(MappingUtil.mapQueue(queue));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT) {
|
|
||||||
int originalPosition = -1;
|
|
||||||
int fromPosition = -1;
|
|
||||||
int toPosition = -1;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
|
|
||||||
if (originalPosition == -1) {
|
|
||||||
originalPosition = viewHolder.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);
|
|
||||||
recyclerView.getAdapter().notifyItemMoved(fromPosition, toPosition);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
|
||||||
super.clearView(recyclerView, viewHolder);
|
|
||||||
|
|
||||||
if (originalPosition != -1 && fromPosition != -1 && toPosition != -1) {
|
|
||||||
MediaManager.swap(mediaBrowserListenableFuture, playerSongQueueAdapter.getItems(), originalPosition, toPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
originalPosition = -1;
|
|
||||||
fromPosition = -1;
|
|
||||||
toPosition = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
|
|
||||||
MediaManager.remove(mediaBrowserListenableFuture, playerSongQueueAdapter.getItems(), viewHolder.getBindingAdapterPosition());
|
|
||||||
viewHolder.getBindingAdapter().notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
}).attachToRecyclerView(playerQueueRecyclerView);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initMediaListenable() {
|
|
||||||
playerBottomSheetViewModel.getLiveSong().observe(requireActivity(), song -> {
|
|
||||||
if (song != null) {
|
|
||||||
buttonFavorite.setChecked(song.isFavorite());
|
|
||||||
|
|
||||||
buttonFavorite.setOnClickListener(v -> playerBottomSheetViewModel.setFavorite(requireContext(), song));
|
|
||||||
|
|
||||||
buttonFavorite.setOnLongClickListener(v -> {
|
|
||||||
Bundle bundle = new Bundle();
|
|
||||||
bundle.putParcelable("song_object", song);
|
|
||||||
|
|
||||||
RatingDialog dialog = new RatingDialog();
|
|
||||||
dialog.setArguments(bundle);
|
|
||||||
dialog.show(requireActivity().getSupportFragmentManager(), null);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
playerBottomSheetViewModel.refreshSongInfo(requireActivity(), song);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initMusicCommandUnfoldButton() {
|
|
||||||
playerCommandUnfoldButton.setOnClickListener(view -> {
|
|
||||||
if (playerCommandCardview.getVisibility() == View.INVISIBLE || playerCommandCardview.getVisibility() == View.GONE) {
|
|
||||||
playerCommandCardview.setVisibility(View.VISIBLE);
|
|
||||||
} else {
|
|
||||||
playerCommandCardview.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initArtistLabelButton() {
|
|
||||||
playerArtistNameLabel.setOnClickListener(view -> playerBottomSheetViewModel.getLiveArtist().observe(requireActivity(), artist -> {
|
|
||||||
Bundle bundle = new Bundle();
|
|
||||||
bundle.putParcelable("artist_object", artist);
|
|
||||||
NavHostFragment.findNavController(this).navigate(R.id.artistPageFragment, bundle);
|
|
||||||
activity.collapseBottomSheet();
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
public View getPlayerHeader() {
|
public View getPlayerHeader() {
|
||||||
return requireView().findViewById(R.id.player_header_layout);
|
return requireView().findViewById(R.id.player_header_layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void scrollOnTop() {
|
|
||||||
bind.playerNestedScrollView.fullScroll(ScrollView.FOCUS_UP);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void goBackToFirstPage() {
|
|
||||||
playerSongCoverViewPager.setCurrentItem(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isViewPagerInFirstPage() {
|
|
||||||
return playerSongCoverViewPager.getCurrentItem() == 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,206 @@
|
||||||
|
package com.cappielloantonio.play.ui.fragment;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.ToggleButton;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.cardview.widget.CardView;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
import androidx.media3.common.MediaMetadata;
|
||||||
|
import androidx.media3.common.Player;
|
||||||
|
import androidx.media3.session.MediaBrowser;
|
||||||
|
import androidx.media3.session.SessionToken;
|
||||||
|
import androidx.navigation.fragment.NavHostFragment;
|
||||||
|
import androidx.viewpager2.widget.ViewPager2;
|
||||||
|
|
||||||
|
import com.cappielloantonio.play.R;
|
||||||
|
import com.cappielloantonio.play.adapter.PlayerNowPlayingSongAdapter;
|
||||||
|
import com.cappielloantonio.play.databinding.InnerFragmentPlayerControllerBinding;
|
||||||
|
import com.cappielloantonio.play.service.MediaService;
|
||||||
|
import com.cappielloantonio.play.ui.activity.MainActivity;
|
||||||
|
import com.cappielloantonio.play.ui.dialog.RatingDialog;
|
||||||
|
import com.cappielloantonio.play.util.MusicUtil;
|
||||||
|
import com.cappielloantonio.play.viewmodel.PlayerBottomSheetViewModel;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
|
|
||||||
|
public class PlayerControllerFragment extends Fragment {
|
||||||
|
private static final String TAG = "PlayerCoverFragment";
|
||||||
|
|
||||||
|
private InnerFragmentPlayerControllerBinding bind;
|
||||||
|
private ImageView playerMoveDownBottomSheet;
|
||||||
|
private ViewPager2 playerSongCoverViewPager;
|
||||||
|
private ToggleButton buttonFavorite;
|
||||||
|
private ImageButton playerCommandUnfoldButton;
|
||||||
|
private CardView playerCommandCardview;
|
||||||
|
private TextView playerSongTitleLabel;
|
||||||
|
private TextView playerArtistNameLabel;
|
||||||
|
|
||||||
|
private MainActivity activity;
|
||||||
|
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
|
||||||
|
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
activity = (MainActivity) getActivity();
|
||||||
|
|
||||||
|
bind = InnerFragmentPlayerControllerBinding.inflate(inflater, container, false);
|
||||||
|
View view = bind.getRoot();
|
||||||
|
|
||||||
|
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
|
||||||
|
|
||||||
|
init();
|
||||||
|
initCoverLyricsSlideView();
|
||||||
|
initMediaListenable();
|
||||||
|
initMusicCommandUnfoldButton();
|
||||||
|
initArtistLabelButton();
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
initializeBrowser();
|
||||||
|
bindMediaController();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStop() {
|
||||||
|
releaseBrowser();
|
||||||
|
super.onStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
bind = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
|
private void init() {
|
||||||
|
playerMoveDownBottomSheet = bind.getRoot().findViewById(R.id.player_move_down_bottom_sheet);
|
||||||
|
playerSongCoverViewPager = bind.getRoot().findViewById(R.id.player_song_cover_view_pager);
|
||||||
|
buttonFavorite = bind.getRoot().findViewById(R.id.button_favorite);
|
||||||
|
playerCommandUnfoldButton = bind.getRoot().findViewById(R.id.player_command_unfold_button);
|
||||||
|
playerCommandCardview = bind.getRoot().findViewById(R.id.player_command_cardview);
|
||||||
|
playerSongTitleLabel = bind.getRoot().findViewById(R.id.player_song_title_label);
|
||||||
|
playerArtistNameLabel = bind.getRoot().findViewById(R.id.player_artist_name_label);
|
||||||
|
|
||||||
|
playerMoveDownBottomSheet.setOnClickListener(view -> activity.collapseBottomSheet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
|
private void initializeBrowser() {
|
||||||
|
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void releaseBrowser() {
|
||||||
|
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
|
private void bindMediaController() {
|
||||||
|
mediaBrowserListenableFuture.addListener(() -> {
|
||||||
|
try {
|
||||||
|
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
|
||||||
|
|
||||||
|
bind.playerControlBodyLayout.setPlayer(mediaBrowser);
|
||||||
|
|
||||||
|
setMediaControllerListener(mediaBrowser);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, e.getMessage());
|
||||||
|
}
|
||||||
|
}, MoreExecutors.directExecutor());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
|
private void setMediaControllerListener(MediaBrowser mediaBrowser) {
|
||||||
|
setMetadata(mediaBrowser.getMediaMetadata());
|
||||||
|
|
||||||
|
mediaBrowser.addListener(new Player.Listener() {
|
||||||
|
@Override
|
||||||
|
public void onMediaMetadataChanged(@NonNull MediaMetadata mediaMetadata) {
|
||||||
|
setMetadata(mediaMetadata);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setMetadata(MediaMetadata mediaMetadata) {
|
||||||
|
if (mediaMetadata.extras != null) playerBottomSheetViewModel.setLiveSong(requireActivity(), mediaMetadata.extras.getString("id"));
|
||||||
|
if (mediaMetadata.extras != null) playerBottomSheetViewModel.setLiveArtist(requireActivity(), mediaMetadata.extras.getString("artistId"));
|
||||||
|
|
||||||
|
playerSongTitleLabel.setText(MusicUtil.getReadableString(String.valueOf(mediaMetadata.title)));
|
||||||
|
playerArtistNameLabel.setText(MusicUtil.getReadableString(String.valueOf(mediaMetadata.artist)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initCoverLyricsSlideView() {
|
||||||
|
playerSongCoverViewPager.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL);
|
||||||
|
playerSongCoverViewPager.setAdapter(new PlayerNowPlayingSongAdapter(this));
|
||||||
|
|
||||||
|
playerSongCoverViewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
|
||||||
|
@Override
|
||||||
|
public void onPageSelected(int position) {
|
||||||
|
super.onPageSelected(position);
|
||||||
|
|
||||||
|
if (position == 0) {
|
||||||
|
activity.setBottomSheetDraggableState(true);
|
||||||
|
} else if (position == 1) {
|
||||||
|
activity.setBottomSheetDraggableState(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initMediaListenable() {
|
||||||
|
playerBottomSheetViewModel.getLiveSong().observe(requireActivity(), song -> {
|
||||||
|
if (song != null) {
|
||||||
|
buttonFavorite.setChecked(song.isFavorite());
|
||||||
|
|
||||||
|
buttonFavorite.setOnClickListener(v -> playerBottomSheetViewModel.setFavorite(requireContext(), song));
|
||||||
|
|
||||||
|
buttonFavorite.setOnLongClickListener(v -> {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putParcelable("song_object", song);
|
||||||
|
|
||||||
|
RatingDialog dialog = new RatingDialog();
|
||||||
|
dialog.setArguments(bundle);
|
||||||
|
dialog.show(requireActivity().getSupportFragmentManager(), null);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
playerBottomSheetViewModel.refreshSongInfo(requireActivity(), song);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initMusicCommandUnfoldButton() {
|
||||||
|
playerCommandUnfoldButton.setOnClickListener(view -> {
|
||||||
|
if (playerCommandCardview.getVisibility() == View.INVISIBLE || playerCommandCardview.getVisibility() == View.GONE) {
|
||||||
|
playerCommandCardview.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
playerCommandCardview.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initArtistLabelButton() {
|
||||||
|
playerArtistNameLabel.setOnClickListener(view -> playerBottomSheetViewModel.getLiveArtist().observe(requireActivity(), artist -> {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putParcelable("artist_object", artist);
|
||||||
|
NavHostFragment.findNavController(this).navigate(R.id.artistPageFragment, bundle);
|
||||||
|
activity.collapseBottomSheet();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,36 +10,28 @@ import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
|
||||||
import androidx.media3.common.MediaMetadata;
|
import androidx.media3.common.MediaMetadata;
|
||||||
import androidx.media3.common.Player;
|
import androidx.media3.common.Player;
|
||||||
import androidx.media3.session.MediaBrowser;
|
import androidx.media3.session.MediaBrowser;
|
||||||
import androidx.media3.session.SessionToken;
|
import androidx.media3.session.SessionToken;
|
||||||
|
|
||||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
|
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
|
||||||
import com.cappielloantonio.play.databinding.FragmentPlayerCoverBinding;
|
import com.cappielloantonio.play.databinding.InnerFragmentPlayerCoverBinding;
|
||||||
import com.cappielloantonio.play.glide.CustomGlideRequest;
|
import com.cappielloantonio.play.glide.CustomGlideRequest;
|
||||||
import com.cappielloantonio.play.service.MediaService;
|
import com.cappielloantonio.play.service.MediaService;
|
||||||
import com.cappielloantonio.play.viewmodel.PlayerBottomSheetViewModel;
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.common.util.concurrent.MoreExecutors;
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
|
|
||||||
public class PlayerCoverFragment extends Fragment {
|
public class PlayerCoverFragment extends Fragment {
|
||||||
private static final String TAG = "PlayerCoverFragment";
|
private static final String TAG = "PlayerCoverFragment";
|
||||||
|
|
||||||
private FragmentPlayerCoverBinding bind;
|
private InnerFragmentPlayerCoverBinding bind;
|
||||||
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
|
|
||||||
|
|
||||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
bind = FragmentPlayerCoverBinding.inflate(inflater, container, false);
|
bind = InnerFragmentPlayerCoverBinding.inflate(inflater, container, false);
|
||||||
View view = bind.getRoot();
|
return bind.getRoot();
|
||||||
|
|
||||||
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
|
|
||||||
|
|
||||||
return view;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -11,19 +11,19 @@ import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
import com.cappielloantonio.play.R;
|
import com.cappielloantonio.play.R;
|
||||||
import com.cappielloantonio.play.databinding.FragmentPlayerLyricsBinding;
|
import com.cappielloantonio.play.databinding.InnerFragmentPlayerLyricsBinding;
|
||||||
import com.cappielloantonio.play.util.MusicUtil;
|
import com.cappielloantonio.play.util.MusicUtil;
|
||||||
import com.cappielloantonio.play.viewmodel.PlayerBottomSheetViewModel;
|
import com.cappielloantonio.play.viewmodel.PlayerBottomSheetViewModel;
|
||||||
|
|
||||||
public class PlayerLyricsFragment extends Fragment {
|
public class PlayerLyricsFragment extends Fragment {
|
||||||
private static final String TAG = "PlayerLyricsFragment";
|
private static final String TAG = "PlayerLyricsFragment";
|
||||||
|
|
||||||
private FragmentPlayerLyricsBinding bind;
|
private InnerFragmentPlayerLyricsBinding bind;
|
||||||
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
|
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
bind = FragmentPlayerLyricsBinding.inflate(inflater, container, false);
|
bind = InnerFragmentPlayerLyricsBinding.inflate(inflater, container, false);
|
||||||
View view = bind.getRoot();
|
View view = bind.getRoot();
|
||||||
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
|
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,157 @@
|
||||||
|
package com.cappielloantonio.play.ui.fragment;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
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 androidx.media3.session.MediaBrowser;
|
||||||
|
import androidx.media3.session.SessionToken;
|
||||||
|
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.cappielloantonio.play.adapter.PlayerSongQueueAdapter;
|
||||||
|
import com.cappielloantonio.play.databinding.InnerFragmentPlayerQueueBinding;
|
||||||
|
import com.cappielloantonio.play.service.MediaManager;
|
||||||
|
import com.cappielloantonio.play.service.MediaService;
|
||||||
|
import com.cappielloantonio.play.util.MappingUtil;
|
||||||
|
import com.cappielloantonio.play.viewmodel.PlayerBottomSheetViewModel;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
public class PlayerQueueFragment extends Fragment {
|
||||||
|
private static final String TAG = "PlayerCoverFragment";
|
||||||
|
|
||||||
|
private InnerFragmentPlayerQueueBinding bind;
|
||||||
|
|
||||||
|
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
|
||||||
|
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||||
|
|
||||||
|
private PlayerSongQueueAdapter playerSongQueueAdapter;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
bind = InnerFragmentPlayerQueueBinding.inflate(inflater, container, false);
|
||||||
|
View view = bind.getRoot();
|
||||||
|
|
||||||
|
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
|
||||||
|
|
||||||
|
init();
|
||||||
|
initQueueRecyclerView();
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
initializeBrowser();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
setMediaBrowserListenableFuture();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStop() {
|
||||||
|
releaseBrowser();
|
||||||
|
super.onStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
bind = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
|
private void init() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
|
private void initializeBrowser() {
|
||||||
|
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void releaseBrowser() {
|
||||||
|
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setMediaBrowserListenableFuture() {
|
||||||
|
playerSongQueueAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initQueueRecyclerView() {
|
||||||
|
bind.playerQueueRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||||
|
bind.playerQueueRecyclerView.setHasFixedSize(true);
|
||||||
|
|
||||||
|
playerSongQueueAdapter = new PlayerSongQueueAdapter(requireContext());
|
||||||
|
bind.playerQueueRecyclerView.setAdapter(playerSongQueueAdapter);
|
||||||
|
playerBottomSheetViewModel.getQueueSong().observe(requireActivity(), queue -> {
|
||||||
|
if (queue != null) {
|
||||||
|
playerSongQueueAdapter.setItems(MappingUtil.mapQueue(queue));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT) {
|
||||||
|
int originalPosition = -1;
|
||||||
|
int fromPosition = -1;
|
||||||
|
int toPosition = -1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
|
||||||
|
if (originalPosition == -1) {
|
||||||
|
originalPosition = viewHolder.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);
|
||||||
|
recyclerView.getAdapter().notifyItemMoved(fromPosition, toPosition);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
||||||
|
super.clearView(recyclerView, viewHolder);
|
||||||
|
|
||||||
|
if (originalPosition != -1 && fromPosition != -1 && toPosition != -1) {
|
||||||
|
MediaManager.swap(mediaBrowserListenableFuture, playerSongQueueAdapter.getItems(), originalPosition, toPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
originalPosition = -1;
|
||||||
|
fromPosition = -1;
|
||||||
|
toPosition = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
|
||||||
|
MediaManager.remove(mediaBrowserListenableFuture, playerSongQueueAdapter.getItems(), viewHolder.getBindingAdapterPosition());
|
||||||
|
viewHolder.getBindingAdapter().notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}).attachToRecyclerView(bind.playerQueueRecyclerView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,33 +1,18 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:id="@+id/player_nested_scroll_view"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/cardColor"
|
|
||||||
app:elevation="8dp">
|
app:elevation="8dp">
|
||||||
|
|
||||||
<FrameLayout
|
<include
|
||||||
|
android:id="@+id/player_body_layout"
|
||||||
|
layout="@layout/player_body_bottom_sheet"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
<androidx.media3.ui.PlayerControlView
|
<include
|
||||||
android:id="@+id/player_body_layout"
|
android:id="@+id/player_header_layout"
|
||||||
android:layout_width="match_parent"
|
layout="@layout/player_header_bottom_sheet"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="match_parent"
|
||||||
app:controller_layout_id="@layout/player_body_bottom_sheet"
|
android:layout_height="@dimen/bottom_sheet_peek_height" />
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
</FrameLayout>
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:show_timeout="0"/>
|
|
||||||
|
|
||||||
<include
|
|
||||||
android:id="@+id/player_header_layout"
|
|
||||||
layout="@layout/player_header_bottom_sheet"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/bottom_sheet_peek_height"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
</FrameLayout>
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.media3.ui.PlayerControlView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/player_control_body_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:controller_layout_id="@layout/player_control_view_body"
|
||||||
|
app:show_timeout="0"/>
|
||||||
14
app/src/main/res/layout/inner_fragment_player_queue.xml
Normal file
14
app/src/main/res/layout/inner_fragment_player_queue.xml
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
<com.cappielloantonio.play.helper.recyclerview.NestedScrollableHost xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/player_queue_recycler_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingTop="12dp"
|
||||||
|
android:paddingBottom="@dimen/global_padding_bottom" />
|
||||||
|
|
||||||
|
</com.cappielloantonio.play.helper.recyclerview.NestedScrollableHost>
|
||||||
|
|
@ -1,259 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.viewpager2.widget.ViewPager2 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_view_pager"
|
||||||
android:id="@+id/player_body_bottom_sheet_layout"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/colorPrimary"
|
android:background="@color/colorPrimary" />
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
|
||||||
android:id="@+id/appbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar"
|
|
||||||
app:elevation="0dp">
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
|
||||||
android:id="@+id/toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@color/colorPrimary"
|
|
||||||
app:popupTheme="@style/ThemeOverlay.MaterialComponents.Light">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/player_move_down_bottom_sheet"
|
|
||||||
android:layout_width="32dp"
|
|
||||||
android:layout_height="32dp"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:background="@drawable/ic_bottom_sheet_down"
|
|
||||||
android:foreground="?android:attr/selectableItemBackgroundBorderless" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/ToolbarTextView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingStart="8dp"
|
|
||||||
android:paddingEnd="8dp"
|
|
||||||
android:text="@string/player_bottom_sheet_title" />
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.appcompat.widget.Toolbar>
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<androidx.viewpager2.widget.ViewPager2
|
|
||||||
android:id="@+id/player_song_cover_view_pager"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
app:layout_constraintDimensionRatio="H,1:1"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/player_big_timer"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="22dp"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/exo_position"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_marginLeft="24dp"
|
|
||||||
android:paddingTop="4dp"
|
|
||||||
android:text="@string/label_placeholder"
|
|
||||||
android:textColor="@color/titleTextColor"
|
|
||||||
android:textSize="12sp" />
|
|
||||||
|
|
||||||
<androidx.media3.ui.DefaultTimeBar
|
|
||||||
android:id="@id/exo_progress"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_marginLeft="4dp"
|
|
||||||
android:layout_marginRight="4dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
app:bar_height="2dp"
|
|
||||||
app:buffered_color="@color/seekBarBufferedColor"
|
|
||||||
app:played_color="@color/seekBarPlayedColor"
|
|
||||||
app:scrubber_color="@color/seekBarPlayedColor"
|
|
||||||
app:scrubber_dragged_size="8dp"
|
|
||||||
app:unplayed_color="@color/seekBarUnPlayedColor" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/exo_duration"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_marginRight="24dp"
|
|
||||||
android:paddingTop="4dp"
|
|
||||||
android:text="@string/label_placeholder"
|
|
||||||
android:textColor="@color/titleTextColor"
|
|
||||||
android:textSize="12sp" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/player_song_title_label"
|
|
||||||
style="@style/TitleTextView"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="12dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:paddingStart="24dp"
|
|
||||||
android:paddingEnd="24dp"
|
|
||||||
android:text="@string/label_placeholder" />
|
|
||||||
|
|
||||||
<ToggleButton
|
|
||||||
android:id="@+id/button_favorite"
|
|
||||||
android:layout_width="26dp"
|
|
||||||
android:layout_height="26dp"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:layout_marginEnd="24dp"
|
|
||||||
android:background="@drawable/button_favorite_selector"
|
|
||||||
android:checked="false"
|
|
||||||
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
|
||||||
android:text=""
|
|
||||||
android:textOff=""
|
|
||||||
android:textOn="" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/player_artist_name_label"
|
|
||||||
style="@style/SubheadTextView"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingStart="24dp"
|
|
||||||
android:paddingEnd="24dp"
|
|
||||||
android:paddingBottom="12dp"
|
|
||||||
android:text="@string/label_placeholder" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/player_music_command_sector"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0.5dp"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:layout_marginStart="18dp"
|
|
||||||
android:layout_marginEnd="18dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:background="@color/dividerColor" />
|
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/player_command_unfold_button"
|
|
||||||
android:layout_width="18dp"
|
|
||||||
android:layout_height="18dp"
|
|
||||||
android:background="@drawable/ic_unfold"
|
|
||||||
android:scaleType="fitCenter" />
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0.5dp"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:layout_marginStart="18dp"
|
|
||||||
android:layout_marginEnd="18dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:background="@color/dividerColor" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
|
||||||
android:id="@+id/player_command_cardview"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:backgroundTint="@color/colorPrimary"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:cardElevation="0dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:clipChildren="false"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/exo_prev"
|
|
||||||
android:layout_width="48dp"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:layout_marginEnd="24dp"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:scaleType="fitCenter"
|
|
||||||
app:srcCompat="@drawable/ic_skip_previous" />
|
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@id/exo_play"
|
|
||||||
style="@style/ExoMediaButton.Play"
|
|
||||||
android:layout_width="48dp"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:scaleType="fitCenter"
|
|
||||||
android:src="@drawable/ic_play" />
|
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@id/exo_pause"
|
|
||||||
style="@style/ExoMediaButton.Pause"
|
|
||||||
android:layout_width="48dp"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:scaleType="fitCenter"
|
|
||||||
android:src="@drawable/ic_pause" />
|
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/exo_next"
|
|
||||||
android:layout_width="48dp"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:layout_marginStart="24dp"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:scaleType="fitCenter"
|
|
||||||
app:srcCompat="@drawable/ic_skip_next" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/player_divider_bottom"
|
|
||||||
style="@style/Divider"
|
|
||||||
android:layout_marginStart="18dp"
|
|
||||||
android:layout_marginTop="12dp"
|
|
||||||
android:layout_marginEnd="18dp" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.cardview.widget.CardView>
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/player_queue_recycler_view"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
android:paddingTop="12dp"
|
|
||||||
android:paddingBottom="@dimen/global_padding_bottom" />
|
|
||||||
</LinearLayout>
|
|
||||||
250
app/src/main/res/layout/player_control_view_body.xml
Normal file
250
app/src/main/res/layout/player_control_view_body.xml
Normal file
|
|
@ -0,0 +1,250 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout 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="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:id="@+id/appbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar"
|
||||||
|
app:elevation="0dp">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/colorPrimary"
|
||||||
|
app:popupTheme="@style/ThemeOverlay.MaterialComponents.Light">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/player_move_down_bottom_sheet"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:background="@drawable/ic_bottom_sheet_down"
|
||||||
|
android:foreground="?android:attr/selectableItemBackgroundBorderless" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/ToolbarTextView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:text="@string/player_bottom_sheet_title" />
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.appcompat.widget.Toolbar>
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
|
android:id="@+id/player_song_cover_view_pager"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintDimensionRatio="H,1:1"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/player_big_timer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="22dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/exo_position"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginLeft="24dp"
|
||||||
|
android:paddingTop="4dp"
|
||||||
|
android:text="@string/label_placeholder"
|
||||||
|
android:textColor="@color/titleTextColor"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
|
||||||
|
<androidx.media3.ui.DefaultTimeBar
|
||||||
|
android:id="@id/exo_progress"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginLeft="4dp"
|
||||||
|
android:layout_marginRight="4dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
app:bar_height="2dp"
|
||||||
|
app:buffered_color="@color/seekBarBufferedColor"
|
||||||
|
app:played_color="@color/seekBarPlayedColor"
|
||||||
|
app:scrubber_color="@color/seekBarPlayedColor"
|
||||||
|
app:scrubber_dragged_size="8dp"
|
||||||
|
app:unplayed_color="@color/seekBarUnPlayedColor" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/exo_duration"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginRight="24dp"
|
||||||
|
android:paddingTop="4dp"
|
||||||
|
android:text="@string/label_placeholder"
|
||||||
|
android:textColor="@color/titleTextColor"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/player_song_title_label"
|
||||||
|
style="@style/TitleTextView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:paddingStart="24dp"
|
||||||
|
android:paddingEnd="24dp"
|
||||||
|
android:text="@string/label_placeholder" />
|
||||||
|
|
||||||
|
<ToggleButton
|
||||||
|
android:id="@+id/button_favorite"
|
||||||
|
android:layout_width="26dp"
|
||||||
|
android:layout_height="26dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:background="@drawable/button_favorite_selector"
|
||||||
|
android:checked="false"
|
||||||
|
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||||
|
android:text=""
|
||||||
|
android:textOff=""
|
||||||
|
android:textOn="" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/player_artist_name_label"
|
||||||
|
style="@style/SubheadTextView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="24dp"
|
||||||
|
android:paddingEnd="24dp"
|
||||||
|
android:paddingBottom="12dp"
|
||||||
|
android:text="@string/label_placeholder" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/player_music_command_sector"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0.5dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginStart="18dp"
|
||||||
|
android:layout_marginEnd="18dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@color/dividerColor" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/player_command_unfold_button"
|
||||||
|
android:layout_width="18dp"
|
||||||
|
android:layout_height="18dp"
|
||||||
|
android:background="@drawable/ic_unfold"
|
||||||
|
android:scaleType="fitCenter" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0.5dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginStart="18dp"
|
||||||
|
android:layout_marginEnd="18dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@color/dividerColor" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:id="@+id/player_command_cardview"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:backgroundTint="@color/colorPrimary"
|
||||||
|
android:visibility="visible"
|
||||||
|
app:cardElevation="0dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:clipChildren="false"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/exo_prev"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
app:srcCompat="@drawable/ic_skip_previous" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@id/exo_play"
|
||||||
|
style="@style/ExoMediaButton.Play"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:src="@drawable/ic_play" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@id/exo_pause"
|
||||||
|
style="@style/ExoMediaButton.Pause"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:src="@drawable/ic_pause" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/exo_next"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginStart="24dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
app:srcCompat="@drawable/ic_skip_next" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/player_divider_bottom"
|
||||||
|
style="@style/Divider"
|
||||||
|
android:layout_marginStart="18dp"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:layout_marginEnd="18dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
</LinearLayout>
|
||||||
97
app/src/main/res/layout/player_control_view_header.xml
Normal file
97
app/src/main/res/layout/player_control_view_header.xml
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/bottom_sheet_peek_height"
|
||||||
|
android:background="@color/almostCardColor"
|
||||||
|
android:clipChildren="false"
|
||||||
|
android:elevation="2dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/player_header_song_cover_image"
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/exo_progress"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/player_header_song_title_label"
|
||||||
|
style="@style/ItemTitleTextView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginEnd="64dp"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/exo_next"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/player_header_song_cover_image"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/player_header_song_artist_label"
|
||||||
|
style="@style/ItemSubtitleTextView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:fontFamily="@font/opensans"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginEnd="64dp"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/exo_next"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/player_header_song_cover_image"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/player_header_song_title_label" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/exo_play"
|
||||||
|
android:layout_width="28dp"
|
||||||
|
android:layout_height="28dp"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:background="@drawable/ic_play"
|
||||||
|
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/exo_progress"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/exo_next"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/exo_pause"
|
||||||
|
android:layout_width="28dp"
|
||||||
|
android:layout_height="28dp"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:background="@drawable/ic_pause"
|
||||||
|
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/exo_progress"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/exo_next"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/exo_next"
|
||||||
|
android:layout_width="28dp"
|
||||||
|
android:layout_height="28dp"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:background="@drawable/ic_skip_next"
|
||||||
|
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/exo_progress"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<androidx.media3.ui.DefaultTimeBar
|
||||||
|
android:id="@id/exo_progress"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="2dp"
|
||||||
|
app:bar_height="2dp"
|
||||||
|
app:buffered_color="@color/seekBarBufferedColor"
|
||||||
|
app:played_color="@color/seekBarPlayedColor"
|
||||||
|
app:scrubber_color="@color/seekBarPlayedColor"
|
||||||
|
app:unplayed_color="@color/seekBarUnPlayedColor"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue