diff --git a/.idea/misc.xml b/.idea/misc.xml
index 2aa0a570..dbd49dc3 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -79,11 +79,17 @@
+
+
+
+
+
+
@@ -115,9 +121,12 @@
-
+
-
+
+
+
+
diff --git a/app/src/main/java/com/cappielloantonio/play/adapter/PlayerBodyAdapter.java b/app/src/main/java/com/cappielloantonio/play/adapter/PlayerBodyAdapter.java
new file mode 100644
index 00000000..ad5f3d17
--- /dev/null
+++ b/app/src/main/java/com/cappielloantonio/play/adapter/PlayerBodyAdapter.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/cappielloantonio/play/adapter/PlayerSongQueueAdapter.java b/app/src/main/java/com/cappielloantonio/play/adapter/PlayerSongQueueAdapter.java
index ee387276..8feb8a37 100644
--- a/app/src/main/java/com/cappielloantonio/play/adapter/PlayerSongQueueAdapter.java
+++ b/app/src/main/java/com/cappielloantonio/play/adapter/PlayerSongQueueAdapter.java
@@ -27,15 +27,13 @@ public class PlayerSongQueueAdapter extends RecyclerView.Adapter mediaBrowserListenableFuture;
private List songs;
- public PlayerSongQueueAdapter(Context context, PlayerBottomSheetFragment playerBottomSheetFragment) {
+ public PlayerSongQueueAdapter(Context context) {
this.context = context;
- this.playerBottomSheetFragment = playerBottomSheetFragment;
this.mInflater = LayoutInflater.from(context);
this.songs = new ArrayList<>();
}
diff --git a/app/src/main/java/com/cappielloantonio/play/helper/recyclerview/NestedScrollableHost.kt b/app/src/main/java/com/cappielloantonio/play/helper/recyclerview/NestedScrollableHost.kt
new file mode 100644
index 00000000..caacffe3
--- /dev/null
+++ b/app/src/main/java/com/cappielloantonio/play/helper/recyclerview/NestedScrollableHost.kt
@@ -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)
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/cappielloantonio/play/ui/activity/MainActivity.java b/app/src/main/java/com/cappielloantonio/play/ui/activity/MainActivity.java
index 5307602f..44578381 100644
--- a/app/src/main/java/com/cappielloantonio/play/ui/activity/MainActivity.java
+++ b/app/src/main/java/com/cappielloantonio/play/ui/activity/MainActivity.java
@@ -152,18 +152,18 @@ public class MainActivity extends BaseActivity {
break;
case BottomSheetBehavior.STATE_COLLAPSED:
if (playerBottomSheetFragment != null) {
- playerBottomSheetFragment.goBackToFirstPage();
- playerBottomSheetFragment.scrollOnTop();
+ // playerBottomSheetFragment.goBackToFirstPage();
+ // playerBottomSheetFragment.scrollOnTop();
}
case BottomSheetBehavior.STATE_SETTLING:
if (playerBottomSheetFragment != null) {
- playerBottomSheetFragment.scrollOnTop();
+ // playerBottomSheetFragment.scrollOnTop();
}
break;
case BottomSheetBehavior.STATE_EXPANDED:
if (playerBottomSheetFragment != null) {
- playerBottomSheetFragment.scrollOnTop();
- setBottomSheetDraggableState(playerBottomSheetFragment.isViewPagerInFirstPage());
+ // playerBottomSheetFragment.scrollOnTop();
+ // setBottomSheetDraggableState(playerBottomSheetFragment.isViewPagerInFirstPage());
}
case BottomSheetBehavior.STATE_DRAGGING:
case BottomSheetBehavior.STATE_HALF_EXPANDED:
diff --git a/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlayerBottomSheetFragment.java b/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlayerBottomSheetFragment.java
index ac9441b7..96c58cf9 100644
--- a/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlayerBottomSheetFragment.java
+++ b/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlayerBottomSheetFragment.java
@@ -23,6 +23,7 @@ import androidx.media3.common.Player;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.MediaController;
import androidx.media3.session.SessionToken;
+import androidx.media3.ui.PlayerControlView;
import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
@@ -31,6 +32,7 @@ import androidx.viewpager2.widget.ViewPager2;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.cappielloantonio.play.R;
+import com.cappielloantonio.play.adapter.PlayerBodyAdapter;
import com.cappielloantonio.play.adapter.PlayerNowPlayingSongAdapter;
import com.cappielloantonio.play.adapter.PlayerSongQueueAdapter;
import com.cappielloantonio.play.databinding.FragmentPlayerBottomSheetBinding;
@@ -51,20 +53,11 @@ public class PlayerBottomSheetFragment extends Fragment {
private static final String TAG = "PlayerBottomSheetFragment";
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 ListenableFuture mediaBrowserListenableFuture;
- private PlayerSongQueueAdapter playerSongQueueAdapter;
+ // TODO: Collegare la seekbar all'exo_progress
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
@@ -74,19 +67,12 @@ public class PlayerBottomSheetFragment extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
- activity = (MainActivity) getActivity();
-
bind = FragmentPlayerBottomSheetBinding.inflate(inflater, container, false);
View view = bind.getRoot();
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
- init();
- initCoverLyricsSlideView();
- initQueueRecyclerView();
- initMediaListenable();
- initMusicCommandUnfoldButton();
- initArtistLabelButton();
+ initViewPager();
return view;
}
@@ -97,7 +83,6 @@ public class PlayerBottomSheetFragment extends Fragment {
initializeMediaBrowser();
bindMediaController();
- setMediaBrowserListenableFuture();
}
@Override
@@ -112,19 +97,9 @@ public class PlayerBottomSheetFragment extends Fragment {
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);
- 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));
+ private void initViewPager() {
+ bind.playerBodyLayout.playerBodyBottomSheetViewPager.setOrientation(ViewPager2.ORIENTATION_VERTICAL);
+ bind.playerBodyLayout.playerBodyBottomSheetViewPager.setAdapter(new PlayerBodyAdapter(this));
}
@SuppressLint("UnsafeOptInUsageError")
@@ -136,18 +111,12 @@ public class PlayerBottomSheetFragment extends Fragment {
MediaController.releaseFuture(mediaBrowserListenableFuture);
}
- private void setMediaBrowserListenableFuture() {
- playerSongQueueAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
- }
-
@SuppressLint("UnsafeOptInUsageError")
private void bindMediaController() {
mediaBrowserListenableFuture.addListener(() -> {
try {
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
- bind.playerBodyLayout.setPlayer(mediaBrowser);
-
setMediaControllerListener(mediaBrowser);
} catch (Exception e) {
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.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
.from(requireContext(), mediaMetadata.extras.getString("id"), CustomGlideRequest.SONG_PIC, null)
.build()
@@ -226,143 +192,7 @@ public class PlayerBottomSheetFragment extends Fragment {
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() {
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;
- }
}
diff --git a/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlayerControllerFragment.java b/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlayerControllerFragment.java
new file mode 100644
index 00000000..e4230760
--- /dev/null
+++ b/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlayerControllerFragment.java
@@ -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 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();
+ }));
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlayerCoverFragment.java b/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlayerCoverFragment.java
index 7e710a90..b56061bd 100644
--- a/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlayerCoverFragment.java
+++ b/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlayerCoverFragment.java
@@ -10,36 +10,28 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
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 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.service.MediaService;
-import com.cappielloantonio.play.viewmodel.PlayerBottomSheetViewModel;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
public class PlayerCoverFragment extends Fragment {
private static final String TAG = "PlayerCoverFragment";
- private FragmentPlayerCoverBinding bind;
- private PlayerBottomSheetViewModel playerBottomSheetViewModel;
-
+ private InnerFragmentPlayerCoverBinding bind;
private ListenableFuture mediaBrowserListenableFuture;
@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);
-
- return view;
+ bind = InnerFragmentPlayerCoverBinding.inflate(inflater, container, false);
+ return bind.getRoot();
}
@Override
diff --git a/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlayerLyricsFragment.java b/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlayerLyricsFragment.java
index f65f9126..6fdbf30d 100644
--- a/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlayerLyricsFragment.java
+++ b/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlayerLyricsFragment.java
@@ -11,19 +11,19 @@ import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
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.viewmodel.PlayerBottomSheetViewModel;
public class PlayerLyricsFragment extends Fragment {
private static final String TAG = "PlayerLyricsFragment";
- private FragmentPlayerLyricsBinding bind;
+ private InnerFragmentPlayerLyricsBinding bind;
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
@Override
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();
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
diff --git a/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlayerQueueFragment.java b/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlayerQueueFragment.java
new file mode 100644
index 00000000..c0fea4c8
--- /dev/null
+++ b/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlayerQueueFragment.java
@@ -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 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);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_player_bottom_sheet.xml b/app/src/main/res/layout/fragment_player_bottom_sheet.xml
index 44c5a0a2..1e46c503 100644
--- a/app/src/main/res/layout/fragment_player_bottom_sheet.xml
+++ b/app/src/main/res/layout/fragment_player_bottom_sheet.xml
@@ -1,33 +1,18 @@
-
-
-
+ android:layout_height="match_parent" />
-
-
-
-
-
\ No newline at end of file
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/inner_fragment_player_controller.xml b/app/src/main/res/layout/inner_fragment_player_controller.xml
new file mode 100644
index 00000000..0355c830
--- /dev/null
+++ b/app/src/main/res/layout/inner_fragment_player_controller.xml
@@ -0,0 +1,9 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_player_cover.xml b/app/src/main/res/layout/inner_fragment_player_cover.xml
similarity index 100%
rename from app/src/main/res/layout/fragment_player_cover.xml
rename to app/src/main/res/layout/inner_fragment_player_cover.xml
diff --git a/app/src/main/res/layout/fragment_player_lyrics.xml b/app/src/main/res/layout/inner_fragment_player_lyrics.xml
similarity index 100%
rename from app/src/main/res/layout/fragment_player_lyrics.xml
rename to app/src/main/res/layout/inner_fragment_player_lyrics.xml
diff --git a/app/src/main/res/layout/inner_fragment_player_queue.xml b/app/src/main/res/layout/inner_fragment_player_queue.xml
new file mode 100644
index 00000000..9e8a0cc0
--- /dev/null
+++ b/app/src/main/res/layout/inner_fragment_player_queue.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/player_body_bottom_sheet.xml b/app/src/main/res/layout/player_body_bottom_sheet.xml
index 9a95b1c5..c3a60af6 100644
--- a/app/src/main/res/layout/player_body_bottom_sheet.xml
+++ b/app/src/main/res/layout/player_body_bottom_sheet.xml
@@ -1,259 +1,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+ android:background="@color/colorPrimary" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/player_control_view_body.xml b/app/src/main/res/layout/player_control_view_body.xml
new file mode 100644
index 00000000..6883a300
--- /dev/null
+++ b/app/src/main/res/layout/player_control_view_body.xml
@@ -0,0 +1,250 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/player_control_view_header.xml b/app/src/main/res/layout/player_control_view_header.xml
new file mode 100644
index 00000000..f093237e
--- /dev/null
+++ b/app/src/main/res/layout/player_control_view_header.xml
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file