feat: Tap anywhere on the song item to toggle playback (#112)

This commit is contained in:
eddyizm 2025-09-23 12:17:56 -07:00 committed by GitHub
commit eb29dc2fb2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 114 additions and 60 deletions

View file

@ -112,7 +112,7 @@ public class PlayerSongQueueAdapter extends RecyclerView.Adapter<PlayerSongQueue
} else { } else {
holder.item.ratingIndicatorImageView.setVisibility(View.GONE); holder.item.ratingIndicatorImageView.setVisibility(View.GONE);
} }
holder.item.playPauseButton.setOnClickListener(v -> { holder.itemView.setOnClickListener(v -> {
mediaBrowserListenableFuture.addListener(() -> { mediaBrowserListenableFuture.addListener(() -> {
try { try {
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get(); MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
@ -138,23 +138,19 @@ public class PlayerSongQueueAdapter extends RecyclerView.Adapter<PlayerSongQueue
} }
private void bindPlaybackState(@NonNull PlayerSongQueueAdapter.ViewHolder holder, @NonNull Child song) { private void bindPlaybackState(@NonNull PlayerSongQueueAdapter.ViewHolder holder, @NonNull Child song) {
boolean isCurrent = currentPlayingId != null && currentPlayingId.equals(song.getId()) && isPlaying; boolean isCurrent = currentPlayingId != null && currentPlayingId.equals(song.getId());
if (isCurrent) { if (isCurrent) {
holder.item.playPauseButton.setVisibility(View.VISIBLE); holder.item.playPauseIcon.setVisibility(View.VISIBLE);
holder.item.playPauseButton.setChecked(true); if (isPlaying) {
holder.item.coverArtOverlay.setVisibility(View.VISIBLE); holder.item.playPauseIcon.setImageResource(R.drawable.ic_pause);
} else { } else {
boolean sameIdPaused = currentPlayingId != null && currentPlayingId.equals(song.getId()) && !isPlaying; holder.item.playPauseIcon.setImageResource(R.drawable.ic_play);
if (sameIdPaused) {
holder.item.playPauseButton.setVisibility(View.VISIBLE);
holder.item.playPauseButton.setChecked(false);
holder.item.coverArtOverlay.setVisibility(View.VISIBLE);
} else {
holder.item.playPauseButton.setVisibility(View.GONE);
holder.item.playPauseButton.setChecked(false);
holder.item.coverArtOverlay.setVisibility(View.INVISIBLE);
} }
holder.item.coverArtOverlay.setVisibility(View.VISIBLE);
} else {
holder.item.playPauseIcon.setVisibility(View.INVISIBLE);
holder.item.coverArtOverlay.setVisibility(View.INVISIBLE);
} }
} }

View file

@ -34,6 +34,7 @@ import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ExecutionException;
@UnstableApi @UnstableApi
public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAdapter.ViewHolder> implements Filterable { public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAdapter.ViewHolder> implements Filterable {
@ -49,6 +50,7 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
private String currentPlayingId; private String currentPlayingId;
private boolean isPlaying; private boolean isPlaying;
private List<Integer> currentPlayingPositions = Collections.emptyList(); private List<Integer> currentPlayingPositions = Collections.emptyList();
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
private final Filter filtering = new Filter() { private final Filter filtering = new Filter() {
@Override @Override
@ -190,39 +192,26 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
holder.item.ratingIndicatorImageView.setVisibility(View.GONE); holder.item.ratingIndicatorImageView.setVisibility(View.GONE);
} }
holder.item.playPauseButton.setOnClickListener(v -> {
Activity a = (Activity) v.getContext();
View root = a.findViewById(android.R.id.content);
View exoPlayPause = root.findViewById(R.id.exo_play_pause);
if (exoPlayPause != null) exoPlayPause.performClick();
});
bindPlaybackState(holder, song); bindPlaybackState(holder, song);
} }
private void bindPlaybackState(@NonNull ViewHolder holder, @NonNull Child song) { private void bindPlaybackState(@NonNull ViewHolder holder, @NonNull Child song) {
boolean isCurrent = currentPlayingId != null && currentPlayingId.equals(song.getId()) && isPlaying; boolean isCurrent = currentPlayingId != null && currentPlayingId.equals(song.getId());
if (isCurrent) { if (isCurrent) {
holder.item.playPauseButton.setVisibility(View.VISIBLE); holder.item.playPauseIcon.setVisibility(View.VISIBLE);
holder.item.playPauseButton.setChecked(true); if (isPlaying) {
holder.item.playPauseIcon.setImageResource(R.drawable.ic_pause);
} else {
holder.item.playPauseIcon.setImageResource(R.drawable.ic_play);
}
if (!showCoverArt) { if (!showCoverArt) {
holder.item.trackNumberTextView.setVisibility(View.GONE); holder.item.trackNumberTextView.setVisibility(View.INVISIBLE);
} else { } else {
holder.item.coverArtOverlay.setVisibility(View.VISIBLE); holder.item.coverArtOverlay.setVisibility(View.VISIBLE);
} }
} else { } else {
boolean sameIdPaused = currentPlayingId != null && currentPlayingId.equals(song.getId()) && !isPlaying; holder.item.playPauseIcon.setVisibility(View.INVISIBLE);
if (sameIdPaused) {
holder.item.playPauseButton.setVisibility(View.VISIBLE);
holder.item.playPauseButton.setChecked(false);
if (!showCoverArt) {
holder.item.trackNumberTextView.setVisibility(View.GONE);
} else {
holder.item.coverArtOverlay.setVisibility(View.VISIBLE);
}
} else {
holder.item.playPauseButton.setVisibility(View.GONE);
holder.item.playPauseButton.setChecked(false);
if (!showCoverArt) { if (!showCoverArt) {
holder.item.trackNumberTextView.setVisibility(View.VISIBLE); holder.item.trackNumberTextView.setVisibility(View.VISIBLE);
} else { } else {
@ -230,7 +219,6 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
} }
} }
} }
}
@Override @Override
public int getItemCount() { public int getItemCount() {
@ -320,12 +308,30 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
} }
public void onClick() { public void onClick() {
int pos = getBindingAdapterPosition();
Child tappedSong = songs.get(pos);
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(MusicUtil.limitPlayableMedia(songs, getBindingAdapterPosition()))); bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(MusicUtil.limitPlayableMedia(songs, getBindingAdapterPosition())));
bundle.putInt(Constants.ITEM_POSITION, MusicUtil.getPlayableMediaPosition(songs, getBindingAdapterPosition())); bundle.putInt(Constants.ITEM_POSITION, MusicUtil.getPlayableMediaPosition(songs, getBindingAdapterPosition()));
if (tappedSong.getId().equals(currentPlayingId)) {
Log.i("SongHorizontalAdapter", "Tapping on currently playing song, toggling playback");
try{
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
Log.i("SongHorizontalAdapter", "MediaBrowser retrieved, isPlaying: " + isPlaying);
if (isPlaying) {
mediaBrowser.pause();
} else {
mediaBrowser.play();
}
} catch (ExecutionException | InterruptedException e) {
Log.e("SongHorizontalAdapter", "Error getting MediaBrowser", e);
}
} else {
click.onMediaClick(bundle); click.onMediaClick(bundle);
} }
}
private boolean onLongClick() { private boolean onLongClick() {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
@ -352,4 +358,8 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
notifyDataSetChanged(); notifyDataSetChanged();
} }
public void setMediaBrowserListenableFuture(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture) {
this.mediaBrowserListenableFuture = mediaBrowserListenableFuture;
}
} }

View file

@ -99,6 +99,11 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
observePlayback(); observePlayback();
} }
public void onResume() {
super.onResume();
if (songHorizontalAdapter != null) setMediaBrowserListenableFuture();
}
@Override @Override
public void onStop() { public void onStop() {
releaseMediaBrowser(); releaseMediaBrowser();
@ -277,6 +282,7 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
songHorizontalAdapter = new SongHorizontalAdapter(this, false, false, album); songHorizontalAdapter = new SongHorizontalAdapter(this, false, false, album);
bind.songRecyclerView.setAdapter(songHorizontalAdapter); bind.songRecyclerView.setAdapter(songHorizontalAdapter);
setMediaBrowserListenableFuture();
reapplyPlayback(); reapplyPlayback();
albumPageViewModel.getAlbumSongLiveList().observe(getViewLifecycleOwner(), songs -> { albumPageViewModel.getAlbumSongLiveList().observe(getViewLifecycleOwner(), songs -> {
@ -328,4 +334,8 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
songHorizontalAdapter.setPlaybackState(id, playing != null && playing); songHorizontalAdapter.setPlaybackState(id, playing != null && playing);
} }
} }
private void setMediaBrowserListenableFuture() {
songHorizontalAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
}
} }

View file

@ -83,6 +83,11 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
observePlayback(); observePlayback();
} }
public void onResume() {
super.onResume();
if (songHorizontalAdapter != null) setMediaBrowserListenableFuture();
}
@Override @Override
public void onStop() { public void onStop() {
releaseMediaBrowser(); releaseMediaBrowser();
@ -175,6 +180,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
songHorizontalAdapter = new SongHorizontalAdapter(this, true, true, null); songHorizontalAdapter = new SongHorizontalAdapter(this, true, true, null);
bind.mostStreamedSongRecyclerView.setAdapter(songHorizontalAdapter); bind.mostStreamedSongRecyclerView.setAdapter(songHorizontalAdapter);
setMediaBrowserListenableFuture();
reapplyPlayback(); reapplyPlayback();
artistPageViewModel.getArtistTopSongList().observe(getViewLifecycleOwner(), songs -> { artistPageViewModel.getArtistTopSongList().observe(getViewLifecycleOwner(), songs -> {
if (songs == null) { if (songs == null) {
@ -299,4 +305,8 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
songHorizontalAdapter.setPlaybackState(id, playing != null && playing); songHorizontalAdapter.setPlaybackState(id, playing != null && playing);
} }
} }
private void setMediaBrowserListenableFuture() {
songHorizontalAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
}
} }

View file

@ -151,6 +151,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
refreshSharesView(); refreshSharesView();
if (topSongAdapter != null) setTopSongsMediaBrowserListenableFuture();
if (starredSongAdapter != null) setStarredSongsMediaBrowserListenableFuture();
} }
@Override @Override
@ -484,6 +486,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
topSongAdapter = new SongHorizontalAdapter(this, true, false, null); topSongAdapter = new SongHorizontalAdapter(this, true, false, null);
bind.topSongsRecyclerView.setAdapter(topSongAdapter); bind.topSongsRecyclerView.setAdapter(topSongAdapter);
setTopSongsMediaBrowserListenableFuture();
reapplyTopSongsPlayback(); reapplyTopSongsPlayback();
homeViewModel.getChronologySample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), chronologies -> { homeViewModel.getChronologySample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), chronologies -> {
if (chronologies == null || chronologies.isEmpty()) { if (chronologies == null || chronologies.isEmpty()) {
@ -524,6 +527,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
starredSongAdapter = new SongHorizontalAdapter(this, true, false, null); starredSongAdapter = new SongHorizontalAdapter(this, true, false, null);
bind.starredTracksRecyclerView.setAdapter(starredSongAdapter); bind.starredTracksRecyclerView.setAdapter(starredSongAdapter);
setStarredSongsMediaBrowserListenableFuture();
reapplyStarredSongsPlayback(); reapplyStarredSongsPlayback();
homeViewModel.getStarredTracks(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> { homeViewModel.getStarredTracks(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
if (songs == null) { if (songs == null) {
@ -1102,4 +1106,12 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
topSongAdapter.setPlaybackState(id, playing != null && playing); topSongAdapter.setPlaybackState(id, playing != null && playing);
} }
} }
private void setTopSongsMediaBrowserListenableFuture() {
topSongAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
}
private void setStarredSongsMediaBrowserListenableFuture() {
starredSongAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
}
} }

View file

@ -117,6 +117,12 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
observePlayback(); observePlayback();
} }
@Override
public void onResume() {
super.onResume();
if (songHorizontalAdapter != null) setMediaBrowserListenableFuture();
}
@Override @Override
public void onStop() { public void onStop() {
releaseMediaBrowser(); releaseMediaBrowser();
@ -254,6 +260,7 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
songHorizontalAdapter = new SongHorizontalAdapter(this, true, false, null); songHorizontalAdapter = new SongHorizontalAdapter(this, true, false, null);
bind.songRecyclerView.setAdapter(songHorizontalAdapter); bind.songRecyclerView.setAdapter(songHorizontalAdapter);
setMediaBrowserListenableFuture();
reapplyPlayback(); reapplyPlayback();
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> { playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> {
@ -303,4 +310,8 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
songHorizontalAdapter.setPlaybackState(id, playing != null && playing); songHorizontalAdapter.setPlaybackState(id, playing != null && playing);
} }
} }
private void setMediaBrowserListenableFuture() {
songHorizontalAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
}
} }

View file

@ -81,6 +81,7 @@ public class SearchFragment extends Fragment implements ClickCallback {
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
if (songHorizontalAdapter != null) setMediaBrowserListenableFuture();
} }
@Override @Override
@ -121,6 +122,7 @@ public class SearchFragment extends Fragment implements ClickCallback {
bind.searchResultTracksRecyclerView.setHasFixedSize(true); bind.searchResultTracksRecyclerView.setHasFixedSize(true);
songHorizontalAdapter = new SongHorizontalAdapter(this, true, false, null); songHorizontalAdapter = new SongHorizontalAdapter(this, true, false, null);
setMediaBrowserListenableFuture();
reapplyPlayback(); reapplyPlayback();
bind.searchResultTracksRecyclerView.setAdapter(songHorizontalAdapter); bind.searchResultTracksRecyclerView.setAdapter(songHorizontalAdapter);
@ -321,4 +323,8 @@ public class SearchFragment extends Fragment implements ClickCallback {
songHorizontalAdapter.setPlaybackState(id, playing != null && playing); songHorizontalAdapter.setPlaybackState(id, playing != null && playing);
} }
} }
private void setMediaBrowserListenableFuture() {
songHorizontalAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
}
} }

View file

@ -90,6 +90,12 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
observePlayback(); observePlayback();
} }
@Override
public void onResume() {
super.onResume();
setMediaBrowserListenableFuture();
}
@Override @Override
public void onStop() { public void onStop() {
releaseMediaBrowser(); releaseMediaBrowser();
@ -197,6 +203,7 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
songHorizontalAdapter = new SongHorizontalAdapter(this, true, false, null); songHorizontalAdapter = new SongHorizontalAdapter(this, true, false, null);
bind.songListRecyclerView.setAdapter(songHorizontalAdapter); bind.songListRecyclerView.setAdapter(songHorizontalAdapter);
setMediaBrowserListenableFuture();
reapplyPlayback(); reapplyPlayback();
songListPageViewModel.getSongList().observe(getViewLifecycleOwner(), songs -> { songListPageViewModel.getSongList().observe(getViewLifecycleOwner(), songs -> {
isLoading = false; isLoading = false;
@ -356,4 +363,8 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
songHorizontalAdapter.setPlaybackState(id, playing != null && playing); songHorizontalAdapter.setPlaybackState(id, playing != null && playing);
} }
} }
private void setMediaBrowserListenableFuture() {
songHorizontalAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
}
} }

View file

@ -66,18 +66,12 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/different_disk_divider_sector" /> app:layout_constraintTop_toBottomOf="@+id/different_disk_divider_sector" />
<ToggleButton <ImageView
android:id="@+id/play_pause_button" android:id="@+id/play_pause_icon"
android:layout_width="28dp" android:layout_width="28dp"
android:layout_height="28dp" android:layout_height="28dp"
android:layout_gravity="center"
android:layout_marginStart="28dp" android:layout_marginStart="28dp"
android:visibility="gone"
android:background="@drawable/button_play_pause_selector"
android:checked="false"
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:text=""
android:textOff=""
android:textOn=""
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/different_disk_divider_sector" /> app:layout_constraintTop_toBottomOf="@+id/different_disk_divider_sector" />

View file

@ -31,18 +31,12 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<ToggleButton <ImageView
android:id="@+id/play_pause_button" android:id="@+id/play_pause_icon"
android:layout_width="28dp" android:layout_width="28dp"
android:layout_height="28dp" android:layout_height="28dp"
android:layout_marginStart="14dp" android:layout_gravity="center"
android:visibility="gone" android:layout_margin="14dp"
android:background="@drawable/button_play_pause_selector"
android:checked="false"
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:text=""
android:textOff=""
android:textOn=""
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />