From 52ba783a90b08f7c7f3c42e8efb85d13210aad78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Garc=C3=ADa?= <55400857+jaime-grj@users.noreply.github.com> Date: Mon, 22 Sep 2025 00:15:52 +0200 Subject: [PATCH 1/9] feat: Mark currently playing song in SongHorizontalAdapter --- .../tempo/interfaces/MediaSongIdCallback.java | 8 +++++++ .../tempo/service/MediaManager.java | 20 ++++++++++++++++ .../ui/adapter/SongHorizontalAdapter.java | 24 +++++++++++++++++++ .../tempo/ui/fragment/AlbumPageFragment.java | 14 +++++++++++ .../tempo/ui/fragment/ArtistPageFragment.java | 14 +++++++++++ .../ui/fragment/HomeTabMusicFragment.java | 18 ++++++++++++++ .../ui/fragment/PlaylistPageFragment.java | 14 +++++++++++ .../tempo/ui/fragment/SearchFragment.java | 14 +++++++++++ .../ui/fragment/SongListPageFragment.java | 14 +++++++++++ .../main/res/layout/item_horizontal_track.xml | 22 +++++++++++++++++ 10 files changed, 162 insertions(+) create mode 100644 app/src/main/java/com/cappielloantonio/tempo/interfaces/MediaSongIdCallback.java diff --git a/app/src/main/java/com/cappielloantonio/tempo/interfaces/MediaSongIdCallback.java b/app/src/main/java/com/cappielloantonio/tempo/interfaces/MediaSongIdCallback.java new file mode 100644 index 00000000..de147c58 --- /dev/null +++ b/app/src/main/java/com/cappielloantonio/tempo/interfaces/MediaSongIdCallback.java @@ -0,0 +1,8 @@ +package com.cappielloantonio.tempo.interfaces; + +import androidx.annotation.Keep; + +@Keep +public interface MediaSongIdCallback { + default void onRecovery(String id) {} +} diff --git a/app/src/main/java/com/cappielloantonio/tempo/service/MediaManager.java b/app/src/main/java/com/cappielloantonio/tempo/service/MediaManager.java index ea1dcaba..9c9947ba 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/service/MediaManager.java +++ b/app/src/main/java/com/cappielloantonio/tempo/service/MediaManager.java @@ -12,6 +12,7 @@ import androidx.media3.session.SessionToken; import com.cappielloantonio.tempo.App; import com.cappielloantonio.tempo.interfaces.MediaIndexCallback; +import com.cappielloantonio.tempo.interfaces.MediaSongIdCallback; import com.cappielloantonio.tempo.model.Chronology; import com.cappielloantonio.tempo.repository.ChronologyRepository; import com.cappielloantonio.tempo.repository.QueueRepository; @@ -292,6 +293,25 @@ public class MediaManager { } } + public static void getCurrentSongId(ListenableFuture mediaBrowserListenableFuture, MediaSongIdCallback callback) { + if (mediaBrowserListenableFuture != null) { + mediaBrowserListenableFuture.addListener(() -> { + try { + if (mediaBrowserListenableFuture.isDone()) { + MediaItem currentItem = mediaBrowserListenableFuture.get().getCurrentMediaItem(); + if (currentItem != null) { + callback.onRecovery(currentItem.mediaMetadata.extras.getString("id")); + } else { + callback.onRecovery(null); + } + } + } catch (ExecutionException | InterruptedException e) { + e.printStackTrace(); + } + }, MoreExecutors.directExecutor()); + } + } + public static void setLastPlayedTimestamp(MediaItem mediaItem) { if (mediaItem != null) getQueueRepository().setLastPlayedTimestamp(mediaItem.mediaId); } diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/SongHorizontalAdapter.java b/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/SongHorizontalAdapter.java index a1630bca..c770b607 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/SongHorizontalAdapter.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/SongHorizontalAdapter.java @@ -10,12 +10,15 @@ import android.widget.Filterable; import androidx.annotation.NonNull; import androidx.appcompat.content.res.AppCompatResources; import androidx.media3.common.util.UnstableApi; +import androidx.media3.session.MediaBrowser; import androidx.recyclerview.widget.RecyclerView; import com.cappielloantonio.tempo.R; import com.cappielloantonio.tempo.databinding.ItemHorizontalTrackBinding; import com.cappielloantonio.tempo.glide.CustomGlideRequest; import com.cappielloantonio.tempo.interfaces.ClickCallback; +import com.cappielloantonio.tempo.interfaces.MediaSongIdCallback; +import com.cappielloantonio.tempo.service.MediaManager; import com.cappielloantonio.tempo.subsonic.models.AlbumID3; import com.cappielloantonio.tempo.subsonic.models.Child; import com.cappielloantonio.tempo.subsonic.models.DiscTitle; @@ -23,6 +26,7 @@ import com.cappielloantonio.tempo.util.Constants; import com.cappielloantonio.tempo.util.DownloadUtil; import com.cappielloantonio.tempo.util.MusicUtil; import com.cappielloantonio.tempo.util.Preferences; +import com.google.common.util.concurrent.ListenableFuture; import java.util.ArrayList; import java.util.Collections; @@ -41,6 +45,7 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter songsFull; private List songs; private String currentFilter; + private ListenableFuture mediaBrowserListenableFuture; private final Filter filtering = new Filter() { @Override @@ -165,6 +170,21 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter mediaBrowserListenableFuture) { + this.mediaBrowserListenableFuture = mediaBrowserListenableFuture; + } } diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumPageFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumPageFragment.java index 03e71e10..a7326b09 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumPageFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumPageFragment.java @@ -93,6 +93,12 @@ public class AlbumPageFragment extends Fragment implements ClickCallback { initializeMediaBrowser(); } + @Override + public void onResume() { + super.onResume(); + setMediaBrowserListenableFuture(); + } + @Override public void onStop() { releaseMediaBrowser(); @@ -271,6 +277,7 @@ public class AlbumPageFragment extends Fragment implements ClickCallback { songHorizontalAdapter = new SongHorizontalAdapter(this, false, false, album); bind.songRecyclerView.setAdapter(songHorizontalAdapter); + setMediaBrowserListenableFuture(); albumPageViewModel.getAlbumSongLiveList().observe(getViewLifecycleOwner(), songs -> songHorizontalAdapter.setItems(songs)); } @@ -288,6 +295,7 @@ public class AlbumPageFragment extends Fragment implements ClickCallback { @Override public void onMediaClick(Bundle bundle) { MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION)); + songHorizontalAdapter.notifyDataSetChanged(); activity.setBottomSheetInPeek(true); } @@ -295,4 +303,10 @@ public class AlbumPageFragment extends Fragment implements ClickCallback { public void onMediaLongClick(Bundle bundle) { Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle); } + + private void setMediaBrowserListenableFuture() { + if (songHorizontalAdapter != null) { + songHorizontalAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture); + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/ArtistPageFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/ArtistPageFragment.java index 9dccc83e..65473ec4 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/ArtistPageFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/ArtistPageFragment.java @@ -82,6 +82,12 @@ public class ArtistPageFragment extends Fragment implements ClickCallback { initializeMediaBrowser(); } + @Override + public void onResume() { + super.onResume(); + setMediaBrowserListenableFuture(); + } + @Override public void onStop() { releaseMediaBrowser(); @@ -174,6 +180,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback { songHorizontalAdapter = new SongHorizontalAdapter(this, true, true, null); bind.mostStreamedSongRecyclerView.setAdapter(songHorizontalAdapter); + setMediaBrowserListenableFuture(); artistPageViewModel.getArtistTopSongList().observe(getViewLifecycleOwner(), songs -> { if (songs == null) { if (bind != null) bind.artistPageTopSongsSector.setVisibility(View.GONE); @@ -246,6 +253,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback { @Override public void onMediaClick(Bundle bundle) { MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION)); + songHorizontalAdapter.notifyDataSetChanged(); activity.setBottomSheetInPeek(true); } @@ -273,4 +281,10 @@ public class ArtistPageFragment extends Fragment implements ClickCallback { public void onArtistLongClick(Bundle bundle) { Navigation.findNavController(requireView()).navigate(R.id.artistBottomSheetDialog, bundle); } + + private void setMediaBrowserListenableFuture() { + if (songHorizontalAdapter != null) { + songHorizontalAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture); + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeTabMusicFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeTabMusicFragment.java index 4d30ce30..c7ba117d 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeTabMusicFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeTabMusicFragment.java @@ -144,6 +144,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback { public void onResume() { super.onResume(); refreshSharesView(); + setTopSongMediaBrowserListenableFuture(); + setStarredSongMediaBrowserListenableFuture(); } @Override @@ -477,6 +479,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback { topSongAdapter = new SongHorizontalAdapter(this, true, false, null); bind.topSongsRecyclerView.setAdapter(topSongAdapter); + setTopSongMediaBrowserListenableFuture(); homeViewModel.getChronologySample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), chronologies -> { if (chronologies == null || chronologies.isEmpty()) { if (bind != null) bind.homeGridTracksSector.setVisibility(View.GONE); @@ -515,6 +518,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback { starredSongAdapter = new SongHorizontalAdapter(this, true, false, null); bind.starredTracksRecyclerView.setAdapter(starredSongAdapter); + setStarredSongMediaBrowserListenableFuture(); homeViewModel.getStarredTracks(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> { if (songs == null) { if (bind != null) bind.starredTracksSector.setVisibility(View.GONE); @@ -954,6 +958,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback { MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION)); activity.setBottomSheetInPeek(true); } + topSongAdapter.notifyDataSetChanged(); + starredSongAdapter.notifyDataSetChanged(); } @Override @@ -1043,4 +1049,16 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback { public void onShareLongClick(Bundle bundle) { Navigation.findNavController(requireView()).navigate(R.id.shareBottomSheetDialog, bundle); } + + private void setTopSongMediaBrowserListenableFuture() { + if (topSongAdapter != null) { + topSongAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture); + } + } + + private void setStarredSongMediaBrowserListenableFuture() { + if (starredSongAdapter != null) { + starredSongAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture); + } + } } diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlaylistPageFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlaylistPageFragment.java index 55b46ff2..9da26370 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlaylistPageFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlaylistPageFragment.java @@ -111,6 +111,12 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback { initializeMediaBrowser(); } + @Override + public void onResume() { + super.onResume(); + setMediaBrowserListenableFuture(); + } + @Override public void onStop() { releaseMediaBrowser(); @@ -248,6 +254,7 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback { songHorizontalAdapter = new SongHorizontalAdapter(this, true, false, null); bind.songRecyclerView.setAdapter(songHorizontalAdapter); + setMediaBrowserListenableFuture(); playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> songHorizontalAdapter.setItems(songs)); } @@ -263,6 +270,7 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback { @Override public void onMediaClick(Bundle bundle) { MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION)); + songHorizontalAdapter.notifyDataSetChanged(); activity.setBottomSheetInPeek(true); } @@ -270,4 +278,10 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback { public void onMediaLongClick(Bundle bundle) { Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle); } + + private void setMediaBrowserListenableFuture() { + if (songHorizontalAdapter != null) { + songHorizontalAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture); + } + } } diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SearchFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SearchFragment.java index 8792a98a..a67c6c02 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SearchFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SearchFragment.java @@ -75,6 +75,12 @@ public class SearchFragment extends Fragment implements ClickCallback { initializeMediaBrowser(); } + @Override + public void onResume() { + super.onResume(); + setMediaBrowserListenableFuture(); + } + @Override public void onStop() { releaseMediaBrowser(); @@ -113,6 +119,7 @@ public class SearchFragment extends Fragment implements ClickCallback { bind.searchResultTracksRecyclerView.setHasFixedSize(true); songHorizontalAdapter = new SongHorizontalAdapter(this, true, false, null); + setMediaBrowserListenableFuture(); bind.searchResultTracksRecyclerView.setAdapter(songHorizontalAdapter); } @@ -260,6 +267,7 @@ public class SearchFragment extends Fragment implements ClickCallback { @Override public void onMediaClick(Bundle bundle) { MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION)); + songHorizontalAdapter.notifyDataSetChanged(); activity.setBottomSheetInPeek(true); } @@ -287,4 +295,10 @@ public class SearchFragment extends Fragment implements ClickCallback { public void onArtistLongClick(Bundle bundle) { Navigation.findNavController(requireView()).navigate(R.id.artistBottomSheetDialog, bundle); } + + private void setMediaBrowserListenableFuture() { + if (songHorizontalAdapter != null) { + songHorizontalAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture); + } + } } diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SongListPageFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SongListPageFragment.java index fcaeec84..3c75f1da 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SongListPageFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SongListPageFragment.java @@ -84,6 +84,12 @@ public class SongListPageFragment extends Fragment implements ClickCallback { initializeMediaBrowser(); } + @Override + public void onResume() { + super.onResume(); + setMediaBrowserListenableFuture(); + } + @Override public void onStop() { releaseMediaBrowser(); @@ -191,6 +197,7 @@ public class SongListPageFragment extends Fragment implements ClickCallback { songHorizontalAdapter = new SongHorizontalAdapter(this, true, false, null); bind.songListRecyclerView.setAdapter(songHorizontalAdapter); + setMediaBrowserListenableFuture(); songListPageViewModel.getSongList().observe(getViewLifecycleOwner(), songs -> { isLoading = false; songHorizontalAdapter.setItems(songs); @@ -318,6 +325,7 @@ public class SongListPageFragment extends Fragment implements ClickCallback { public void onMediaClick(Bundle bundle) { hideKeyboard(requireView()); MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION)); + songHorizontalAdapter.notifyDataSetChanged(); activity.setBottomSheetInPeek(true); } @@ -325,4 +333,10 @@ public class SongListPageFragment extends Fragment implements ClickCallback { public void onMediaLongClick(Bundle bundle) { Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle); } + + private void setMediaBrowserListenableFuture() { + if (songHorizontalAdapter != null) { + songHorizontalAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture); + } + } } \ No newline at end of file diff --git a/app/src/main/res/layout/item_horizontal_track.xml b/app/src/main/res/layout/item_horizontal_track.xml index dfea07a9..cc0bd8b6 100644 --- a/app/src/main/res/layout/item_horizontal_track.xml +++ b/app/src/main/res/layout/item_horizontal_track.xml @@ -55,6 +55,28 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/different_disk_divider_sector" /> + + + + Date: Mon, 22 Sep 2025 00:35:23 +0200 Subject: [PATCH 2/9] feat: Mark currently playing song in PlayerSongQueueAdapter --- .../ui/adapter/PlayerSongQueueAdapter.java | 14 ++++++++++++ .../ui/fragment/PlayerQueueFragment.java | 2 ++ .../res/layout/item_player_queue_song.xml | 22 +++++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/PlayerSongQueueAdapter.java b/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/PlayerSongQueueAdapter.java index 5747eab1..c155174b 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/PlayerSongQueueAdapter.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/PlayerSongQueueAdapter.java @@ -17,6 +17,7 @@ import com.cappielloantonio.tempo.databinding.ItemPlayerQueueSongBinding; import com.cappielloantonio.tempo.glide.CustomGlideRequest; import com.cappielloantonio.tempo.interfaces.ClickCallback; import com.cappielloantonio.tempo.interfaces.MediaIndexCallback; +import com.cappielloantonio.tempo.interfaces.MediaSongIdCallback; import com.cappielloantonio.tempo.service.MediaManager; import com.cappielloantonio.tempo.subsonic.models.Child; import com.cappielloantonio.tempo.util.Constants; @@ -86,6 +87,19 @@ public class PlayerSongQueueAdapter extends RecyclerView.Adapter { if (queue != null) { playerSongQueueAdapter.setItems(queue.stream().map(item -> (Child) item).collect(Collectors.toList())); @@ -215,5 +216,6 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback { @Override public void onMediaClick(Bundle bundle) { MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION)); + updateNowPlayingItem(); } } \ No newline at end of file diff --git a/app/src/main/res/layout/item_player_queue_song.xml b/app/src/main/res/layout/item_player_queue_song.xml index 01dd6096..ff6752ed 100644 --- a/app/src/main/res/layout/item_player_queue_song.xml +++ b/app/src/main/res/layout/item_player_queue_song.xml @@ -20,6 +20,28 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + + + Date: Mon, 22 Sep 2025 01:31:34 +0200 Subject: [PATCH 3/9] fix: Use proper play icon --- app/src/main/res/layout/item_horizontal_track.xml | 2 +- app/src/main/res/layout/item_player_queue_song.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/layout/item_horizontal_track.xml b/app/src/main/res/layout/item_horizontal_track.xml index cc0bd8b6..69420c51 100644 --- a/app/src/main/res/layout/item_horizontal_track.xml +++ b/app/src/main/res/layout/item_horizontal_track.xml @@ -75,7 +75,7 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/different_disk_divider_sector" - android:src="@drawable/button_play_pause_selector" /> + android:src="@drawable/ic_play" /> + android:src="@drawable/ic_play" /> Date: Mon, 22 Sep 2025 19:28:01 +0200 Subject: [PATCH 4/9] feat: Add play/pause button in song lists --- .../tempo/interfaces/MediaSongIdCallback.java | 8 -- .../tempo/service/MediaManager.java | 98 ++++++++++--- .../ui/adapter/PlayerSongQueueAdapter.java | 107 ++++++++++++-- .../ui/adapter/SongHorizontalAdapter.java | 133 ++++++++++++++---- .../tempo/ui/fragment/AlbumPageFragment.java | 39 +++-- .../tempo/ui/fragment/ArtistPageFragment.java | 36 +++-- .../ui/fragment/HomeTabMusicFragment.java | 61 ++++++-- .../ui/fragment/PlayerQueueFragment.java | 39 ++++- .../ui/fragment/PlaylistPageFragment.java | 39 +++-- .../tempo/ui/fragment/SearchFragment.java | 31 +++- .../ui/fragment/SongListPageFragment.java | 35 +++-- .../tempo/viewmodel/PlaybackViewModel.java | 38 +++++ .../main/res/layout/item_horizontal_track.xml | 13 +- .../res/layout/item_player_queue_song.xml | 13 +- 14 files changed, 555 insertions(+), 135 deletions(-) delete mode 100644 app/src/main/java/com/cappielloantonio/tempo/interfaces/MediaSongIdCallback.java create mode 100644 app/src/main/java/com/cappielloantonio/tempo/viewmodel/PlaybackViewModel.java diff --git a/app/src/main/java/com/cappielloantonio/tempo/interfaces/MediaSongIdCallback.java b/app/src/main/java/com/cappielloantonio/tempo/interfaces/MediaSongIdCallback.java deleted file mode 100644 index de147c58..00000000 --- a/app/src/main/java/com/cappielloantonio/tempo/interfaces/MediaSongIdCallback.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.cappielloantonio.tempo.interfaces; - -import androidx.annotation.Keep; - -@Keep -public interface MediaSongIdCallback { - default void onRecovery(String id) {} -} diff --git a/app/src/main/java/com/cappielloantonio/tempo/service/MediaManager.java b/app/src/main/java/com/cappielloantonio/tempo/service/MediaManager.java index 9c9947ba..538e1dd5 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/service/MediaManager.java +++ b/app/src/main/java/com/cappielloantonio/tempo/service/MediaManager.java @@ -2,17 +2,20 @@ package com.cappielloantonio.tempo.service; import android.content.ComponentName; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.OptIn; +import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LiveData; import androidx.lifecycle.Observer; import androidx.media3.common.MediaItem; +import androidx.media3.common.Player; import androidx.media3.common.util.UnstableApi; import androidx.media3.session.MediaBrowser; import androidx.media3.session.SessionToken; import com.cappielloantonio.tempo.App; import com.cappielloantonio.tempo.interfaces.MediaIndexCallback; -import com.cappielloantonio.tempo.interfaces.MediaSongIdCallback; import com.cappielloantonio.tempo.model.Chronology; import com.cappielloantonio.tempo.repository.ChronologyRepository; import com.cappielloantonio.tempo.repository.QueueRepository; @@ -22,14 +25,88 @@ import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation; import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode; import com.cappielloantonio.tempo.util.MappingUtil; import com.cappielloantonio.tempo.util.Preferences; +import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; +import java.lang.ref.WeakReference; import java.util.List; import java.util.concurrent.ExecutionException; public class MediaManager { private static final String TAG = "MediaManager"; + private static WeakReference attachedBrowserRef = new WeakReference<>(null); + + /** + * Attach a Player.Listener to the MediaBrowser (once per browser instance). + * Safe to call every time you (re)create the MediaBrowser future (e.g. in Fragment.onStart()). + */ + public static void registerPlaybackObserver( + LifecycleOwner lifecycleOwner, + ListenableFuture browserFuture, + PlaybackViewModel playbackViewModel + ) { + if (browserFuture == null) return; + + Futures.addCallback(browserFuture, new FutureCallback() { + @Override + public void onSuccess(MediaBrowser browser) { + MediaBrowser current = attachedBrowserRef.get(); + if (current != browser) { + browser.addListener(new Player.Listener() { + @Override + public void onEvents(@NonNull Player player, @NonNull Player.Events events) { + if (events.contains(Player.EVENT_MEDIA_ITEM_TRANSITION) + || events.contains(Player.EVENT_PLAY_WHEN_READY_CHANGED) + || events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED)) { + + String mediaId = player.getCurrentMediaItem() != null + ? player.getCurrentMediaItem().mediaId + : null; + + boolean playing = player.getPlaybackState() == Player.STATE_READY + && player.getPlayWhenReady(); + + playbackViewModel.update(mediaId, playing); + } + } + }); + + String mediaId = browser.getCurrentMediaItem() != null + ? browser.getCurrentMediaItem().mediaId + : null; + boolean playing = browser.getPlaybackState() == Player.STATE_READY && browser.getPlayWhenReady(); + playbackViewModel.update(mediaId, playing); + + attachedBrowserRef = new WeakReference<>(browser); + } else { + String mediaId = browser.getCurrentMediaItem() != null + ? browser.getCurrentMediaItem().mediaId + : null; + boolean playing = browser.getPlaybackState() == Player.STATE_READY && browser.getPlayWhenReady(); + playbackViewModel.update(mediaId, playing); + } + } + + @Override + public void onFailure(@NonNull Throwable t) { + // Log or handle if needed + } + }, MoreExecutors.directExecutor()); + } + + /** + * Call this when you truly want to discard the browser (e.g. Activity.onStop()). + * If fragments call it, they should accept that next onStart will recreate a browser & listener. + */ + public static void onBrowserReleased(@Nullable MediaBrowser released) { + MediaBrowser attached = attachedBrowserRef.get(); + if (attached == released) { + attachedBrowserRef.clear(); + } + } public static void reset(ListenableFuture mediaBrowserListenableFuture) { if (mediaBrowserListenableFuture != null) { @@ -293,25 +370,6 @@ public class MediaManager { } } - public static void getCurrentSongId(ListenableFuture mediaBrowserListenableFuture, MediaSongIdCallback callback) { - if (mediaBrowserListenableFuture != null) { - mediaBrowserListenableFuture.addListener(() -> { - try { - if (mediaBrowserListenableFuture.isDone()) { - MediaItem currentItem = mediaBrowserListenableFuture.get().getCurrentMediaItem(); - if (currentItem != null) { - callback.onRecovery(currentItem.mediaMetadata.extras.getString("id")); - } else { - callback.onRecovery(null); - } - } - } catch (ExecutionException | InterruptedException e) { - e.printStackTrace(); - } - }, MoreExecutors.directExecutor()); - } - } - public static void setLastPlayedTimestamp(MediaItem mediaItem) { if (mediaItem != null) getQueueRepository().setLastPlayedTimestamp(mediaItem.mediaId); } diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/PlayerSongQueueAdapter.java b/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/PlayerSongQueueAdapter.java index c155174b..e6f25d66 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/PlayerSongQueueAdapter.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/PlayerSongQueueAdapter.java @@ -1,7 +1,9 @@ package com.cappielloantonio.tempo.ui.adapter; +import android.app.Activity; import android.graphics.drawable.Drawable; import android.os.Bundle; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -17,24 +19,30 @@ import com.cappielloantonio.tempo.databinding.ItemPlayerQueueSongBinding; import com.cappielloantonio.tempo.glide.CustomGlideRequest; import com.cappielloantonio.tempo.interfaces.ClickCallback; import com.cappielloantonio.tempo.interfaces.MediaIndexCallback; -import com.cappielloantonio.tempo.interfaces.MediaSongIdCallback; import com.cappielloantonio.tempo.service.MediaManager; import com.cappielloantonio.tempo.subsonic.models.Child; import com.cappielloantonio.tempo.util.Constants; import com.cappielloantonio.tempo.util.MusicUtil; import com.cappielloantonio.tempo.util.Preferences; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; public class PlayerSongQueueAdapter extends RecyclerView.Adapter { + private static final String TAG = "PlayerSongQueueAdapter"; private final ClickCallback click; private ListenableFuture mediaBrowserListenableFuture; private List songs; + private String currentPlayingId; + private boolean isPlaying; + private List currentPlayingPositions = Collections.emptyList(); + public PlayerSongQueueAdapter(ClickCallback click) { this.click = click; this.songs = Collections.emptyList(); @@ -87,19 +95,6 @@ public class PlayerSongQueueAdapter extends RecyclerView.Adapter { + mediaBrowserListenableFuture.addListener(() -> { + try { + MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get(); + int pos = holder.getBindingAdapterPosition(); + Child s = songs.get(pos); + if (currentPlayingId != null && currentPlayingId.equals(s.getId())) { + if (isPlaying) { + mediaBrowser.pause(); + } else { + mediaBrowser.play(); + } + } else { + mediaBrowser.seekTo(pos, 0); + mediaBrowser.play(); + } + } catch (Exception e) { + Log.w(TAG, "Error obtaining MediaBrowser", e); + } + }, MoreExecutors.directExecutor()); + + }); + bindPlaybackState(holder, song); + } + + private void bindPlaybackState(@NonNull PlayerSongQueueAdapter.ViewHolder holder, @NonNull Child song) { + boolean isCurrent = currentPlayingId != null && currentPlayingId.equals(song.getId()) && isPlaying; + + if (isCurrent) { + holder.item.playPauseButton.setVisibility(View.VISIBLE); + holder.item.playPauseButton.setChecked(true); + holder.item.coverArtOverlay.setVisibility(View.VISIBLE); + } else { + boolean sameIdPaused = currentPlayingId != null && currentPlayingId.equals(song.getId()) && !isPlaying; + 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); + } + } } public List getItems() { @@ -146,6 +185,46 @@ public class PlayerSongQueueAdapter extends RecyclerView.Adapter oldPositions = currentPlayingPositions; + + this.currentPlayingId = mediaId; + this.isPlaying = playing; + + if (Objects.equals(oldId, mediaId) && oldPlaying == playing) { + List newPositionsCheck = mediaId != null ? findPositionsById(mediaId) : Collections.emptyList(); + if (oldPositions.equals(newPositionsCheck)) { + return; + } + } + + currentPlayingPositions = mediaId != null ? findPositionsById(mediaId) : Collections.emptyList(); + + for (int pos : oldPositions) { + if (pos >= 0 && pos < songs.size()) { + notifyItemChanged(pos, "payload_playback"); + } + } + for (int pos : currentPlayingPositions) { + if (!oldPositions.contains(pos) && pos >= 0 && pos < songs.size()) { + notifyItemChanged(pos, "payload_playback"); + } + } + } + + private List findPositionsById(String id) { + if (id == null) return Collections.emptyList(); + List positions = new ArrayList<>(); + for (int i = 0; i < songs.size(); i++) { + if (id.equals(songs.get(i).getId())) { + positions.add(i); + } + } + return positions; + } + public Child getItem(int id) { return songs.get(id); } diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/SongHorizontalAdapter.java b/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/SongHorizontalAdapter.java index c770b607..f782c553 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/SongHorizontalAdapter.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/SongHorizontalAdapter.java @@ -1,6 +1,8 @@ package com.cappielloantonio.tempo.ui.adapter; +import android.app.Activity; import android.os.Bundle; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -17,8 +19,6 @@ import com.cappielloantonio.tempo.R; import com.cappielloantonio.tempo.databinding.ItemHorizontalTrackBinding; import com.cappielloantonio.tempo.glide.CustomGlideRequest; import com.cappielloantonio.tempo.interfaces.ClickCallback; -import com.cappielloantonio.tempo.interfaces.MediaSongIdCallback; -import com.cappielloantonio.tempo.service.MediaManager; import com.cappielloantonio.tempo.subsonic.models.AlbumID3; import com.cappielloantonio.tempo.subsonic.models.Child; import com.cappielloantonio.tempo.subsonic.models.DiscTitle; @@ -45,7 +45,10 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter songsFull; private List songs; private String currentFilter; - private ListenableFuture mediaBrowserListenableFuture; + + private String currentPlayingId; + private boolean isPlaying; + private List currentPlayingPositions = Collections.emptyList(); private final Filter filtering = new Filter() { @Override @@ -75,6 +78,12 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter) results.values; notifyDataSetChanged(); + + for (int pos : currentPlayingPositions) { + if (pos >= 0 && pos < songs.size()) { + notifyItemChanged(pos, "payload_playback"); + } + } } }; @@ -86,6 +95,7 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter payloads) { + if (!payloads.isEmpty() && payloads.contains("payload_playback")) { + bindPlaybackState(holder, songs.get(position)); + } else { + super.onBindViewHolder(holder, position, payloads); + } + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { Child song = songs.get(position); holder.item.searchResultSongTitleTextView.setText(song.getTitle()); @@ -171,20 +190,46 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter { + 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); + } + + private void bindPlaybackState(@NonNull ViewHolder holder, @NonNull Child song) { + boolean isCurrent = currentPlayingId != null && currentPlayingId.equals(song.getId()) && isPlaying; + + if (isCurrent) { + holder.item.playPauseButton.setVisibility(View.VISIBLE); + holder.item.playPauseButton.setChecked(true); + if (!showCoverArt) { + holder.item.trackNumberTextView.setVisibility(View.GONE); + } else { + holder.item.coverArtOverlay.setVisibility(View.VISIBLE); + } + } else { + boolean sameIdPaused = currentPlayingId != null && currentPlayingId.equals(song.getId()) && !isPlaying; + if (sameIdPaused) { + holder.item.playPauseButton.setVisibility(View.VISIBLE); + holder.item.playPauseButton.setChecked(false); + if (!showCoverArt) { + holder.item.trackNumberTextView.setVisibility(View.GONE); } else { - holder.item.playPauseIcon.setVisibility(View.INVISIBLE); - if (showCoverArt) holder.item.coverArtOverlay.setVisibility(View.INVISIBLE); - if (!showCoverArt) holder.item.trackNumberTextView.setVisibility(View.VISIBLE); + holder.item.coverArtOverlay.setVisibility(View.VISIBLE); + } + } else { + holder.item.playPauseButton.setVisibility(View.GONE); + holder.item.playPauseButton.setChecked(false); + if (!showCoverArt) { + holder.item.trackNumberTextView.setVisibility(View.VISIBLE); + } else { + holder.item.coverArtOverlay.setVisibility(View.INVISIBLE); } } - }); + } } @Override @@ -195,7 +240,6 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter songs) { this.songsFull = songs != null ? songs : Collections.emptyList(); filtering.filter(currentFilter); - notifyDataSetChanged(); } @Override @@ -208,6 +252,46 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter oldPositions = currentPlayingPositions; + + this.currentPlayingId = mediaId; + this.isPlaying = playing; + + if (Objects.equals(oldId, mediaId) && oldPlaying == playing) { + List newPositionsCheck = mediaId != null ? findPositionsById(mediaId) : Collections.emptyList(); + if (oldPositions.equals(newPositionsCheck)) { + return; + } + } + + currentPlayingPositions = mediaId != null ? findPositionsById(mediaId) : Collections.emptyList(); + + for (int pos : oldPositions) { + if (pos >= 0 && pos < songs.size()) { + notifyItemChanged(pos, "payload_playback"); + } + } + for (int pos : currentPlayingPositions) { + if (!oldPositions.contains(pos) && pos >= 0 && pos < songs.size()) { + notifyItemChanged(pos, "payload_playback"); + } + } + } + + private List findPositionsById(String id) { + if (id == null) return Collections.emptyList(); + List positions = new ArrayList<>(); + for (int i = 0; i < songs.size(); i++) { + if (id.equals(songs.get(i).getId())) { + positions.add(i); + } + } + return positions; + } + @Override public Filter getFilter() { return filtering; @@ -236,18 +320,21 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter(MusicUtil.limitPlayableMedia(songs, getBindingAdapterPosition()))); - bundle.putInt(Constants.ITEM_POSITION, MusicUtil.getPlayableMediaPosition(songs, getBindingAdapterPosition())); - + bundle.putParcelableArrayList( + Constants.TRACKS_OBJECT, + new ArrayList<>(MusicUtil.limitPlayableMedia(songs, getBindingAdapterPosition())) + ); + bundle.putInt( + Constants.ITEM_POSITION, + MusicUtil.getPlayableMediaPosition(songs, getBindingAdapterPosition()) + ); click.onMediaClick(bundle); } private boolean onLongClick() { Bundle bundle = new Bundle(); bundle.putParcelable(Constants.TRACK_OBJECT, songs.get(getBindingAdapterPosition())); - click.onMediaLongClick(bundle); - return true; } } @@ -267,8 +354,4 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter mediaBrowserListenableFuture) { - this.mediaBrowserListenableFuture = mediaBrowserListenableFuture; - } } diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumPageFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumPageFragment.java index a7326b09..e2eb22e1 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumPageFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumPageFragment.java @@ -40,6 +40,7 @@ import com.cappielloantonio.tempo.util.DownloadUtil; import com.cappielloantonio.tempo.util.MappingUtil; import com.cappielloantonio.tempo.util.MusicUtil; import com.cappielloantonio.tempo.viewmodel.AlbumPageViewModel; +import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel; import com.google.common.util.concurrent.ListenableFuture; import java.util.ArrayList; @@ -52,6 +53,7 @@ public class AlbumPageFragment extends Fragment implements ClickCallback { private FragmentAlbumPageBinding bind; private MainActivity activity; private AlbumPageViewModel albumPageViewModel; + private PlaybackViewModel playbackViewModel; private SongHorizontalAdapter songHorizontalAdapter; private ListenableFuture mediaBrowserListenableFuture; @@ -74,6 +76,7 @@ public class AlbumPageFragment extends Fragment implements ClickCallback { bind = FragmentAlbumPageBinding.inflate(inflater, container, false); View view = bind.getRoot(); albumPageViewModel = new ViewModelProvider(requireActivity()).get(AlbumPageViewModel.class); + playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class); init(); initAppBar(); @@ -91,12 +94,9 @@ public class AlbumPageFragment extends Fragment implements ClickCallback { super.onStart(); initializeMediaBrowser(); - } - @Override - public void onResume() { - super.onResume(); - setMediaBrowserListenableFuture(); + MediaManager.registerPlaybackObserver(getViewLifecycleOwner(), mediaBrowserListenableFuture, playbackViewModel); + observePlayback(); } @Override @@ -277,9 +277,12 @@ public class AlbumPageFragment extends Fragment implements ClickCallback { songHorizontalAdapter = new SongHorizontalAdapter(this, false, false, album); bind.songRecyclerView.setAdapter(songHorizontalAdapter); - setMediaBrowserListenableFuture(); + reapplyPlayback(); - albumPageViewModel.getAlbumSongLiveList().observe(getViewLifecycleOwner(), songs -> songHorizontalAdapter.setItems(songs)); + albumPageViewModel.getAlbumSongLiveList().observe(getViewLifecycleOwner(), songs -> { + songHorizontalAdapter.setItems(songs); + reapplyPlayback(); + }); } }); } @@ -295,7 +298,6 @@ public class AlbumPageFragment extends Fragment implements ClickCallback { @Override public void onMediaClick(Bundle bundle) { MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION)); - songHorizontalAdapter.notifyDataSetChanged(); activity.setBottomSheetInPeek(true); } @@ -304,9 +306,26 @@ public class AlbumPageFragment extends Fragment implements ClickCallback { Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle); } - private void setMediaBrowserListenableFuture() { + private void observePlayback() { + playbackViewModel.getCurrentMediaId().observe(getViewLifecycleOwner(), id -> { + if (songHorizontalAdapter != null) { + Boolean playing = playbackViewModel.getIsPlaying().getValue(); + songHorizontalAdapter.setPlaybackState(id, playing != null && playing); + } + }); + playbackViewModel.getIsPlaying().observe(getViewLifecycleOwner(), playing -> { + if (songHorizontalAdapter != null) { + String id = playbackViewModel.getCurrentMediaId().getValue(); + songHorizontalAdapter.setPlaybackState(id, playing != null && playing); + } + }); + } + + private void reapplyPlayback() { if (songHorizontalAdapter != null) { - songHorizontalAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture); + String id = playbackViewModel.getCurrentMediaId().getValue(); + Boolean playing = playbackViewModel.getIsPlaying().getValue(); + songHorizontalAdapter.setPlaybackState(id, playing != null && playing); } } } \ No newline at end of file diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/ArtistPageFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/ArtistPageFragment.java index 65473ec4..ec9456cb 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/ArtistPageFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/ArtistPageFragment.java @@ -38,6 +38,7 @@ import com.cappielloantonio.tempo.util.Constants; import com.cappielloantonio.tempo.util.MusicUtil; import com.cappielloantonio.tempo.util.Preferences; import com.cappielloantonio.tempo.viewmodel.ArtistPageViewModel; +import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel; import com.google.common.util.concurrent.ListenableFuture; import java.util.ArrayList; @@ -49,6 +50,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback { private FragmentArtistPageBinding bind; private MainActivity activity; private ArtistPageViewModel artistPageViewModel; + private PlaybackViewModel playbackViewModel; private SongHorizontalAdapter songHorizontalAdapter; private AlbumCatalogueAdapter albumCatalogueAdapter; @@ -63,6 +65,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback { bind = FragmentArtistPageBinding.inflate(inflater, container, false); View view = bind.getRoot(); artistPageViewModel = new ViewModelProvider(requireActivity()).get(ArtistPageViewModel.class); + playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class); init(); initAppBar(); @@ -80,12 +83,8 @@ public class ArtistPageFragment extends Fragment implements ClickCallback { super.onStart(); initializeMediaBrowser(); - } - - @Override - public void onResume() { - super.onResume(); - setMediaBrowserListenableFuture(); + MediaManager.registerPlaybackObserver(getViewLifecycleOwner(), mediaBrowserListenableFuture, playbackViewModel); + observePlayback(); } @Override @@ -180,7 +179,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback { songHorizontalAdapter = new SongHorizontalAdapter(this, true, true, null); bind.mostStreamedSongRecyclerView.setAdapter(songHorizontalAdapter); - setMediaBrowserListenableFuture(); + reapplyPlayback(); artistPageViewModel.getArtistTopSongList().observe(getViewLifecycleOwner(), songs -> { if (songs == null) { if (bind != null) bind.artistPageTopSongsSector.setVisibility(View.GONE); @@ -190,6 +189,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback { if (bind != null) bind.artistPageShuffleButton.setEnabled(!songs.isEmpty()); songHorizontalAdapter.setItems(songs); + reapplyPlayback(); } }); } @@ -253,7 +253,6 @@ public class ArtistPageFragment extends Fragment implements ClickCallback { @Override public void onMediaClick(Bundle bundle) { MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION)); - songHorizontalAdapter.notifyDataSetChanged(); activity.setBottomSheetInPeek(true); } @@ -282,9 +281,26 @@ public class ArtistPageFragment extends Fragment implements ClickCallback { Navigation.findNavController(requireView()).navigate(R.id.artistBottomSheetDialog, bundle); } - private void setMediaBrowserListenableFuture() { + private void observePlayback() { + playbackViewModel.getCurrentMediaId().observe(getViewLifecycleOwner(), id -> { + if (songHorizontalAdapter != null) { + Boolean playing = playbackViewModel.getIsPlaying().getValue(); + songHorizontalAdapter.setPlaybackState(id, playing != null && playing); + } + }); + playbackViewModel.getIsPlaying().observe(getViewLifecycleOwner(), playing -> { + if (songHorizontalAdapter != null) { + String id = playbackViewModel.getCurrentMediaId().getValue(); + songHorizontalAdapter.setPlaybackState(id, playing != null && playing); + } + }); + } + + private void reapplyPlayback() { if (songHorizontalAdapter != null) { - songHorizontalAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture); + String id = playbackViewModel.getCurrentMediaId().getValue(); + Boolean playing = playbackViewModel.getIsPlaying().getValue(); + songHorizontalAdapter.setPlaybackState(id, playing != null && playing); } } } \ No newline at end of file diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeTabMusicFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeTabMusicFragment.java index c7ba117d..b779b0fd 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeTabMusicFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeTabMusicFragment.java @@ -60,6 +60,7 @@ import com.cappielloantonio.tempo.util.MusicUtil; import com.cappielloantonio.tempo.util.Preferences; import com.cappielloantonio.tempo.util.UIUtil; import com.cappielloantonio.tempo.viewmodel.HomeViewModel; +import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel; import com.google.android.material.snackbar.Snackbar; import com.google.common.util.concurrent.ListenableFuture; @@ -74,6 +75,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback { private FragmentHomeTabMusicBinding bind; private MainActivity activity; private HomeViewModel homeViewModel; + private PlaybackViewModel playbackViewModel; private DiscoverSongAdapter discoverSongAdapter; private SimilarTrackAdapter similarMusicAdapter; @@ -101,6 +103,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback { bind = FragmentHomeTabMusicBinding.inflate(inflater, container, false); View view = bind.getRoot(); homeViewModel = new ViewModelProvider(requireActivity()).get(HomeViewModel.class); + playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class); init(); @@ -138,14 +141,16 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback { super.onStart(); initializeMediaBrowser(); + + MediaManager.registerPlaybackObserver(getViewLifecycleOwner(), mediaBrowserListenableFuture, playbackViewModel); + observeStarredSongsPlayback(); + observeTopSongsPlayback(); } @Override public void onResume() { super.onResume(); refreshSharesView(); - setTopSongMediaBrowserListenableFuture(); - setStarredSongMediaBrowserListenableFuture(); } @Override @@ -479,7 +484,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback { topSongAdapter = new SongHorizontalAdapter(this, true, false, null); bind.topSongsRecyclerView.setAdapter(topSongAdapter); - setTopSongMediaBrowserListenableFuture(); + reapplyTopSongsPlayback(); homeViewModel.getChronologySample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), chronologies -> { if (chronologies == null || chronologies.isEmpty()) { if (bind != null) bind.homeGridTracksSector.setVisibility(View.GONE); @@ -495,6 +500,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback { .collect(Collectors.toList()); topSongAdapter.setItems(topSongs); + reapplyTopSongsPlayback(); } }); @@ -518,7 +524,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback { starredSongAdapter = new SongHorizontalAdapter(this, true, false, null); bind.starredTracksRecyclerView.setAdapter(starredSongAdapter); - setStarredSongMediaBrowserListenableFuture(); + reapplyStarredSongsPlayback(); homeViewModel.getStarredTracks(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> { if (songs == null) { if (bind != null) bind.starredTracksSector.setVisibility(View.GONE); @@ -529,6 +535,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback { bind.starredTracksRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(songs.size(), 5), GridLayoutManager.HORIZONTAL, false)); starredSongAdapter.setItems(songs); + reapplyStarredSongsPlayback(); } }); @@ -1050,15 +1057,49 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback { Navigation.findNavController(requireView()).navigate(R.id.shareBottomSheetDialog, bundle); } - private void setTopSongMediaBrowserListenableFuture() { - if (topSongAdapter != null) { - topSongAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture); + private void observeStarredSongsPlayback() { + playbackViewModel.getCurrentMediaId().observe(getViewLifecycleOwner(), id -> { + if (starredSongAdapter != null) { + Boolean playing = playbackViewModel.getIsPlaying().getValue(); + starredSongAdapter.setPlaybackState(id, playing != null && playing); + } + }); + playbackViewModel.getIsPlaying().observe(getViewLifecycleOwner(), playing -> { + if (starredSongAdapter != null) { + String id = playbackViewModel.getCurrentMediaId().getValue(); + starredSongAdapter.setPlaybackState(id, playing != null && playing); + } + }); + } + + private void observeTopSongsPlayback() { + playbackViewModel.getCurrentMediaId().observe(getViewLifecycleOwner(), id -> { + if (topSongAdapter != null) { + Boolean playing = playbackViewModel.getIsPlaying().getValue(); + topSongAdapter.setPlaybackState(id, playing != null && playing); + } + }); + playbackViewModel.getIsPlaying().observe(getViewLifecycleOwner(), playing -> { + if (topSongAdapter != null) { + String id = playbackViewModel.getCurrentMediaId().getValue(); + topSongAdapter.setPlaybackState(id, playing != null && playing); + } + }); + } + + private void reapplyStarredSongsPlayback() { + if (starredSongAdapter != null) { + String id = playbackViewModel.getCurrentMediaId().getValue(); + Boolean playing = playbackViewModel.getIsPlaying().getValue(); + starredSongAdapter.setPlaybackState(id, playing != null && playing); } } - private void setStarredSongMediaBrowserListenableFuture() { - if (starredSongAdapter != null) { - starredSongAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture); + private void reapplyTopSongsPlayback() { + if (topSongAdapter != null) { + String id = playbackViewModel.getCurrentMediaId().getValue(); + Boolean playing = playbackViewModel.getIsPlaying().getValue(); + topSongAdapter.setPlaybackState(id, playing != null && playing); } } } diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerQueueFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerQueueFragment.java index 69e0d4fc..09f91734 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerQueueFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerQueueFragment.java @@ -23,6 +23,7 @@ import com.cappielloantonio.tempo.service.MediaService; import com.cappielloantonio.tempo.subsonic.models.Child; import com.cappielloantonio.tempo.ui.adapter.PlayerSongQueueAdapter; import com.cappielloantonio.tempo.util.Constants; +import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel; import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; @@ -38,6 +39,7 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback { private InnerFragmentPlayerQueueBinding bind; private PlayerBottomSheetViewModel playerBottomSheetViewModel; + private PlaybackViewModel playbackViewModel; private ListenableFuture mediaBrowserListenableFuture; private PlayerSongQueueAdapter playerSongQueueAdapter; @@ -48,6 +50,7 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback { View view = bind.getRoot(); playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class); + playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class); initQueueRecyclerView(); @@ -59,13 +62,15 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback { super.onStart(); initializeBrowser(); bindMediaController(); + + MediaManager.registerPlaybackObserver(getViewLifecycleOwner(), mediaBrowserListenableFuture, playbackViewModel); + observePlayback(); } @Override public void onResume() { super.onResume(); setMediaBrowserListenableFuture(); - updateNowPlayingItem(); } @Override @@ -110,10 +115,12 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback { playerSongQueueAdapter = new PlayerSongQueueAdapter(this); bind.playerQueueRecyclerView.setAdapter(playerSongQueueAdapter); - setMediaBrowserListenableFuture(); + reapplyPlayback(); + playerBottomSheetViewModel.getQueueSong().observe(getViewLifecycleOwner(), queue -> { if (queue != null) { playerSongQueueAdapter.setItems(queue.stream().map(item -> (Child) item).collect(Collectors.toList())); + reapplyPlayback(); } }); @@ -209,13 +216,31 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback { }); } - private void updateNowPlayingItem() { - playerSongQueueAdapter.notifyDataSetChanged(); - } - @Override public void onMediaClick(Bundle bundle) { MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION)); - updateNowPlayingItem(); + } + + private void observePlayback() { + playbackViewModel.getCurrentMediaId().observe(getViewLifecycleOwner(), id -> { + if (playerSongQueueAdapter != null) { + Boolean playing = playbackViewModel.getIsPlaying().getValue(); + playerSongQueueAdapter.setPlaybackState(id, playing != null && playing); + } + }); + playbackViewModel.getIsPlaying().observe(getViewLifecycleOwner(), playing -> { + if (playerSongQueueAdapter != null) { + String id = playbackViewModel.getCurrentMediaId().getValue(); + playerSongQueueAdapter.setPlaybackState(id, playing != null && playing); + } + }); + } + + private void reapplyPlayback() { + if (playerSongQueueAdapter != null) { + String id = playbackViewModel.getCurrentMediaId().getValue(); + Boolean playing = playbackViewModel.getIsPlaying().getValue(); + playerSongQueueAdapter.setPlaybackState(id, playing != null && playing); + } } } \ No newline at end of file diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlaylistPageFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlaylistPageFragment.java index 9da26370..b3eb97ee 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlaylistPageFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlaylistPageFragment.java @@ -37,6 +37,7 @@ import com.cappielloantonio.tempo.util.Constants; import com.cappielloantonio.tempo.util.DownloadUtil; import com.cappielloantonio.tempo.util.MappingUtil; import com.cappielloantonio.tempo.util.MusicUtil; +import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel; import com.cappielloantonio.tempo.viewmodel.PlaylistPageViewModel; import com.google.common.util.concurrent.ListenableFuture; @@ -49,6 +50,7 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback { private FragmentPlaylistPageBinding bind; private MainActivity activity; private PlaylistPageViewModel playlistPageViewModel; + private PlaybackViewModel playbackViewModel; private SongHorizontalAdapter songHorizontalAdapter; @@ -94,6 +96,7 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback { bind = FragmentPlaylistPageBinding.inflate(inflater, container, false); View view = bind.getRoot(); playlistPageViewModel = new ViewModelProvider(requireActivity()).get(PlaylistPageViewModel.class); + playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class); init(); initAppBar(); @@ -109,12 +112,9 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback { super.onStart(); initializeMediaBrowser(); - } - @Override - public void onResume() { - super.onResume(); - setMediaBrowserListenableFuture(); + MediaManager.registerPlaybackObserver(getViewLifecycleOwner(), mediaBrowserListenableFuture, playbackViewModel); + observePlayback(); } @Override @@ -254,9 +254,12 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback { songHorizontalAdapter = new SongHorizontalAdapter(this, true, false, null); bind.songRecyclerView.setAdapter(songHorizontalAdapter); - setMediaBrowserListenableFuture(); + reapplyPlayback(); - playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> songHorizontalAdapter.setItems(songs)); + playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> { + songHorizontalAdapter.setItems(songs); + reapplyPlayback(); + }); } private void initializeMediaBrowser() { @@ -270,7 +273,6 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback { @Override public void onMediaClick(Bundle bundle) { MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION)); - songHorizontalAdapter.notifyDataSetChanged(); activity.setBottomSheetInPeek(true); } @@ -279,9 +281,26 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback { Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle); } - private void setMediaBrowserListenableFuture() { + private void observePlayback() { + playbackViewModel.getCurrentMediaId().observe(getViewLifecycleOwner(), id -> { + if (songHorizontalAdapter != null) { + Boolean playing = playbackViewModel.getIsPlaying().getValue(); + songHorizontalAdapter.setPlaybackState(id, playing != null && playing); + } + }); + playbackViewModel.getIsPlaying().observe(getViewLifecycleOwner(), playing -> { + if (songHorizontalAdapter != null) { + String id = playbackViewModel.getCurrentMediaId().getValue(); + songHorizontalAdapter.setPlaybackState(id, playing != null && playing); + } + }); + } + + private void reapplyPlayback() { if (songHorizontalAdapter != null) { - songHorizontalAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture); + String id = playbackViewModel.getCurrentMediaId().getValue(); + Boolean playing = playbackViewModel.getIsPlaying().getValue(); + songHorizontalAdapter.setPlaybackState(id, playing != null && playing); } } } diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SearchFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SearchFragment.java index a67c6c02..25268f0d 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SearchFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SearchFragment.java @@ -34,6 +34,7 @@ import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter; import com.cappielloantonio.tempo.ui.adapter.ArtistAdapter; import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter; import com.cappielloantonio.tempo.util.Constants; +import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel; import com.cappielloantonio.tempo.viewmodel.SearchViewModel; import com.google.common.util.concurrent.ListenableFuture; @@ -46,6 +47,7 @@ public class SearchFragment extends Fragment implements ClickCallback { private FragmentSearchBinding bind; private MainActivity activity; private SearchViewModel searchViewModel; + private PlaybackViewModel playbackViewModel; private ArtistAdapter artistAdapter; private AlbumAdapter albumAdapter; @@ -61,6 +63,7 @@ public class SearchFragment extends Fragment implements ClickCallback { bind = FragmentSearchBinding.inflate(inflater, container, false); View view = bind.getRoot(); searchViewModel = new ViewModelProvider(requireActivity()).get(SearchViewModel.class); + playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class); initSearchResultView(); initSearchView(); @@ -73,12 +76,14 @@ public class SearchFragment extends Fragment implements ClickCallback { public void onStart() { super.onStart(); initializeMediaBrowser(); + + MediaManager.registerPlaybackObserver(getViewLifecycleOwner(), mediaBrowserListenableFuture, playbackViewModel); + observePlayback(); } @Override public void onResume() { super.onResume(); - setMediaBrowserListenableFuture(); } @Override @@ -119,7 +124,8 @@ public class SearchFragment extends Fragment implements ClickCallback { bind.searchResultTracksRecyclerView.setHasFixedSize(true); songHorizontalAdapter = new SongHorizontalAdapter(this, true, false, null); - setMediaBrowserListenableFuture(); + reapplyPlayback(); + bind.searchResultTracksRecyclerView.setAdapter(songHorizontalAdapter); } @@ -296,9 +302,26 @@ public class SearchFragment extends Fragment implements ClickCallback { Navigation.findNavController(requireView()).navigate(R.id.artistBottomSheetDialog, bundle); } - private void setMediaBrowserListenableFuture() { + private void observePlayback() { + playbackViewModel.getCurrentMediaId().observe(getViewLifecycleOwner(), id -> { + if (songHorizontalAdapter != null) { + Boolean playing = playbackViewModel.getIsPlaying().getValue(); + songHorizontalAdapter.setPlaybackState(id, playing != null && playing); + } + }); + playbackViewModel.getIsPlaying().observe(getViewLifecycleOwner(), playing -> { + if (songHorizontalAdapter != null) { + String id = playbackViewModel.getCurrentMediaId().getValue(); + songHorizontalAdapter.setPlaybackState(id, playing != null && playing); + } + }); + } + + private void reapplyPlayback() { if (songHorizontalAdapter != null) { - songHorizontalAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture); + String id = playbackViewModel.getCurrentMediaId().getValue(); + Boolean playing = playbackViewModel.getIsPlaying().getValue(); + songHorizontalAdapter.setPlaybackState(id, playing != null && playing); } } } diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SongListPageFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SongListPageFragment.java index 3c75f1da..35a09d4c 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SongListPageFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SongListPageFragment.java @@ -36,6 +36,7 @@ import com.cappielloantonio.tempo.subsonic.models.Child; import com.cappielloantonio.tempo.ui.activity.MainActivity; import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter; import com.cappielloantonio.tempo.util.Constants; +import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel; import com.cappielloantonio.tempo.viewmodel.SongListPageViewModel; import com.google.common.util.concurrent.ListenableFuture; @@ -49,6 +50,7 @@ public class SongListPageFragment extends Fragment implements ClickCallback { private FragmentSongListPageBinding bind; private MainActivity activity; private SongListPageViewModel songListPageViewModel; + private PlaybackViewModel playbackViewModel; private SongHorizontalAdapter songHorizontalAdapter; @@ -69,6 +71,7 @@ public class SongListPageFragment extends Fragment implements ClickCallback { bind = FragmentSongListPageBinding.inflate(inflater, container, false); View view = bind.getRoot(); songListPageViewModel = new ViewModelProvider(requireActivity()).get(SongListPageViewModel.class); + playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class); init(); initAppBar(); @@ -82,12 +85,9 @@ public class SongListPageFragment extends Fragment implements ClickCallback { public void onStart() { super.onStart(); initializeMediaBrowser(); - } - @Override - public void onResume() { - super.onResume(); - setMediaBrowserListenableFuture(); + MediaManager.registerPlaybackObserver(getViewLifecycleOwner(), mediaBrowserListenableFuture, playbackViewModel); + observePlayback(); } @Override @@ -197,10 +197,11 @@ public class SongListPageFragment extends Fragment implements ClickCallback { songHorizontalAdapter = new SongHorizontalAdapter(this, true, false, null); bind.songListRecyclerView.setAdapter(songHorizontalAdapter); - setMediaBrowserListenableFuture(); + reapplyPlayback(); songListPageViewModel.getSongList().observe(getViewLifecycleOwner(), songs -> { isLoading = false; songHorizontalAdapter.setItems(songs); + reapplyPlayback(); setSongListPageSubtitle(songs); }); @@ -325,7 +326,6 @@ public class SongListPageFragment extends Fragment implements ClickCallback { public void onMediaClick(Bundle bundle) { hideKeyboard(requireView()); MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION)); - songHorizontalAdapter.notifyDataSetChanged(); activity.setBottomSheetInPeek(true); } @@ -334,9 +334,26 @@ public class SongListPageFragment extends Fragment implements ClickCallback { Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle); } - private void setMediaBrowserListenableFuture() { + private void observePlayback() { + playbackViewModel.getCurrentMediaId().observe(getViewLifecycleOwner(), id -> { + if (songHorizontalAdapter != null) { + Boolean playing = playbackViewModel.getIsPlaying().getValue(); + songHorizontalAdapter.setPlaybackState(id, playing != null && playing); + } + }); + playbackViewModel.getIsPlaying().observe(getViewLifecycleOwner(), playing -> { + if (songHorizontalAdapter != null) { + String id = playbackViewModel.getCurrentMediaId().getValue(); + songHorizontalAdapter.setPlaybackState(id, playing != null && playing); + } + }); + } + + private void reapplyPlayback() { if (songHorizontalAdapter != null) { - songHorizontalAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture); + String id = playbackViewModel.getCurrentMediaId().getValue(); + Boolean playing = playbackViewModel.getIsPlaying().getValue(); + songHorizontalAdapter.setPlaybackState(id, playing != null && playing); } } } \ No newline at end of file diff --git a/app/src/main/java/com/cappielloantonio/tempo/viewmodel/PlaybackViewModel.java b/app/src/main/java/com/cappielloantonio/tempo/viewmodel/PlaybackViewModel.java new file mode 100644 index 00000000..a6733e72 --- /dev/null +++ b/app/src/main/java/com/cappielloantonio/tempo/viewmodel/PlaybackViewModel.java @@ -0,0 +1,38 @@ +package com.cappielloantonio.tempo.viewmodel; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; + +import java.util.Objects; + +public class PlaybackViewModel extends ViewModel { + + private final MutableLiveData currentMediaId = new MutableLiveData<>(null); + private final MutableLiveData isPlaying = new MutableLiveData<>(false); + + // (Optional) expose position or other info + // private final MutableLiveData positionMs = new MutableLiveData<>(0L); + + public LiveData getCurrentMediaId() { + return currentMediaId; + } + + public LiveData getIsPlaying() { + return isPlaying; + } + + public void update(String mediaId, boolean playing) { + if (!Objects.equals(currentMediaId.getValue(), mediaId)) { + currentMediaId.postValue(mediaId); + } + if (!Objects.equals(isPlaying.getValue(), playing)) { + isPlaying.postValue(playing); + } + } + + public void clear() { + currentMediaId.postValue(null); + isPlaying.postValue(false); + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/item_horizontal_track.xml b/app/src/main/res/layout/item_horizontal_track.xml index 69420c51..57711a1c 100644 --- a/app/src/main/res/layout/item_horizontal_track.xml +++ b/app/src/main/res/layout/item_horizontal_track.xml @@ -66,16 +66,21 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/different_disk_divider_sector" /> - + app:layout_constraintTop_toBottomOf="@+id/different_disk_divider_sector" /> - + app:layout_constraintTop_toTopOf="parent" /> Date: Mon, 22 Sep 2025 19:39:53 +0200 Subject: [PATCH 5/9] refactor: Add some code mistakenly removed, remove some comments, remove unused parameter --- .../tempo/service/MediaManager.java | 12 ++---------- .../tempo/ui/adapter/SongHorizontalAdapter.java | 13 +++++-------- .../tempo/ui/fragment/AlbumPageFragment.java | 2 +- .../tempo/ui/fragment/ArtistPageFragment.java | 2 +- .../tempo/ui/fragment/HomeTabMusicFragment.java | 2 +- .../tempo/ui/fragment/PlayerQueueFragment.java | 7 ++++++- .../tempo/ui/fragment/PlaylistPageFragment.java | 2 +- .../tempo/ui/fragment/SearchFragment.java | 2 +- .../tempo/ui/fragment/SongListPageFragment.java | 2 +- 9 files changed, 19 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/com/cappielloantonio/tempo/service/MediaManager.java b/app/src/main/java/com/cappielloantonio/tempo/service/MediaManager.java index 538e1dd5..b8a6008f 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/service/MediaManager.java +++ b/app/src/main/java/com/cappielloantonio/tempo/service/MediaManager.java @@ -1,6 +1,7 @@ package com.cappielloantonio.tempo.service; import android.content.ComponentName; +import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -39,12 +40,7 @@ public class MediaManager { private static final String TAG = "MediaManager"; private static WeakReference attachedBrowserRef = new WeakReference<>(null); - /** - * Attach a Player.Listener to the MediaBrowser (once per browser instance). - * Safe to call every time you (re)create the MediaBrowser future (e.g. in Fragment.onStart()). - */ public static void registerPlaybackObserver( - LifecycleOwner lifecycleOwner, ListenableFuture browserFuture, PlaybackViewModel playbackViewModel ) { @@ -92,15 +88,11 @@ public class MediaManager { @Override public void onFailure(@NonNull Throwable t) { - // Log or handle if needed + Log.e(TAG, "Failed to get MediaBrowser instance", t); } }, MoreExecutors.directExecutor()); } - /** - * Call this when you truly want to discard the browser (e.g. Activity.onStop()). - * If fragments call it, they should accept that next onStart will recreate a browser & listener. - */ public static void onBrowserReleased(@Nullable MediaBrowser released) { MediaBrowser attached = attachedBrowserRef.get(); if (attached == released) { diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/SongHorizontalAdapter.java b/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/SongHorizontalAdapter.java index f782c553..0a83e0e7 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/SongHorizontalAdapter.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/SongHorizontalAdapter.java @@ -240,6 +240,7 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter songs) { this.songsFull = songs != null ? songs : Collections.emptyList(); filtering.filter(currentFilter); + notifyDataSetChanged(); } @Override @@ -320,21 +321,17 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter(MusicUtil.limitPlayableMedia(songs, getBindingAdapterPosition())) - ); - bundle.putInt( - Constants.ITEM_POSITION, - MusicUtil.getPlayableMediaPosition(songs, getBindingAdapterPosition()) - ); + bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(MusicUtil.limitPlayableMedia(songs, getBindingAdapterPosition()))); + bundle.putInt( Constants.ITEM_POSITION, MusicUtil.getPlayableMediaPosition(songs, getBindingAdapterPosition())); click.onMediaClick(bundle); } private boolean onLongClick() { Bundle bundle = new Bundle(); bundle.putParcelable(Constants.TRACK_OBJECT, songs.get(getBindingAdapterPosition())); + click.onMediaLongClick(bundle); + return true; } } diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumPageFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumPageFragment.java index e2eb22e1..e6595e5f 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumPageFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumPageFragment.java @@ -95,7 +95,7 @@ public class AlbumPageFragment extends Fragment implements ClickCallback { initializeMediaBrowser(); - MediaManager.registerPlaybackObserver(getViewLifecycleOwner(), mediaBrowserListenableFuture, playbackViewModel); + MediaManager.registerPlaybackObserver(mediaBrowserListenableFuture, playbackViewModel); observePlayback(); } diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/ArtistPageFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/ArtistPageFragment.java index ec9456cb..11b0a210 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/ArtistPageFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/ArtistPageFragment.java @@ -83,7 +83,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback { super.onStart(); initializeMediaBrowser(); - MediaManager.registerPlaybackObserver(getViewLifecycleOwner(), mediaBrowserListenableFuture, playbackViewModel); + MediaManager.registerPlaybackObserver(mediaBrowserListenableFuture, playbackViewModel); observePlayback(); } diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeTabMusicFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeTabMusicFragment.java index b779b0fd..672dabd4 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeTabMusicFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeTabMusicFragment.java @@ -142,7 +142,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback { initializeMediaBrowser(); - MediaManager.registerPlaybackObserver(getViewLifecycleOwner(), mediaBrowserListenableFuture, playbackViewModel); + MediaManager.registerPlaybackObserver(mediaBrowserListenableFuture, playbackViewModel); observeStarredSongsPlayback(); observeTopSongsPlayback(); } diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerQueueFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerQueueFragment.java index 09f91734..1686cc21 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerQueueFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerQueueFragment.java @@ -63,7 +63,7 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback { initializeBrowser(); bindMediaController(); - MediaManager.registerPlaybackObserver(getViewLifecycleOwner(), mediaBrowserListenableFuture, playbackViewModel); + MediaManager.registerPlaybackObserver(mediaBrowserListenableFuture, playbackViewModel); observePlayback(); } @@ -71,6 +71,7 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback { public void onResume() { super.onResume(); setMediaBrowserListenableFuture(); + updateNowPlayingItem(); } @Override @@ -216,6 +217,10 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback { }); } + private void updateNowPlayingItem() { + playerSongQueueAdapter.notifyDataSetChanged(); + } + @Override public void onMediaClick(Bundle bundle) { MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION)); diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlaylistPageFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlaylistPageFragment.java index b3eb97ee..e51afc56 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlaylistPageFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlaylistPageFragment.java @@ -113,7 +113,7 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback { initializeMediaBrowser(); - MediaManager.registerPlaybackObserver(getViewLifecycleOwner(), mediaBrowserListenableFuture, playbackViewModel); + MediaManager.registerPlaybackObserver(mediaBrowserListenableFuture, playbackViewModel); observePlayback(); } diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SearchFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SearchFragment.java index 25268f0d..7136329e 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SearchFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SearchFragment.java @@ -77,7 +77,7 @@ public class SearchFragment extends Fragment implements ClickCallback { super.onStart(); initializeMediaBrowser(); - MediaManager.registerPlaybackObserver(getViewLifecycleOwner(), mediaBrowserListenableFuture, playbackViewModel); + MediaManager.registerPlaybackObserver(mediaBrowserListenableFuture, playbackViewModel); observePlayback(); } diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SongListPageFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SongListPageFragment.java index 35a09d4c..db6ebe38 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SongListPageFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SongListPageFragment.java @@ -86,7 +86,7 @@ public class SongListPageFragment extends Fragment implements ClickCallback { super.onStart(); initializeMediaBrowser(); - MediaManager.registerPlaybackObserver(getViewLifecycleOwner(), mediaBrowserListenableFuture, playbackViewModel); + MediaManager.registerPlaybackObserver(mediaBrowserListenableFuture, playbackViewModel); observePlayback(); } From f74813ef69230db6a511f319ff5969fe156210c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Garc=C3=ADa?= <55400857+jaime-grj@users.noreply.github.com> Date: Mon, 22 Sep 2025 19:47:07 +0200 Subject: [PATCH 6/9] refactor: Remove commented code --- .../cappielloantonio/tempo/viewmodel/PlaybackViewModel.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/src/main/java/com/cappielloantonio/tempo/viewmodel/PlaybackViewModel.java b/app/src/main/java/com/cappielloantonio/tempo/viewmodel/PlaybackViewModel.java index a6733e72..6f266148 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/viewmodel/PlaybackViewModel.java +++ b/app/src/main/java/com/cappielloantonio/tempo/viewmodel/PlaybackViewModel.java @@ -11,9 +11,6 @@ public class PlaybackViewModel extends ViewModel { private final MutableLiveData currentMediaId = new MutableLiveData<>(null); private final MutableLiveData isPlaying = new MutableLiveData<>(false); - // (Optional) expose position or other info - // private final MutableLiveData positionMs = new MutableLiveData<>(0L); - public LiveData getCurrentMediaId() { return currentMediaId; } From e1c5a60805267f05f29ff5c524750860ef534c2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Garc=C3=ADa?= <55400857+jaime-grj@users.noreply.github.com> Date: Mon, 22 Sep 2025 20:03:02 +0200 Subject: [PATCH 7/9] refactor: Rename methods and variables --- .../tempo/ui/fragment/AlbumPageFragment.java | 6 +++--- .../tempo/ui/fragment/ArtistPageFragment.java | 10 +++------- .../tempo/ui/fragment/HomeTabMusicFragment.java | 12 ++++++------ .../tempo/ui/fragment/PlayerQueueFragment.java | 6 +++--- .../tempo/ui/fragment/PlaylistPageFragment.java | 6 +++--- .../tempo/ui/fragment/SearchFragment.java | 9 +++------ .../tempo/ui/fragment/SongListPageFragment.java | 6 +++--- .../tempo/viewmodel/PlaybackViewModel.java | 14 +++++++------- 8 files changed, 31 insertions(+), 38 deletions(-) diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumPageFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumPageFragment.java index e6595e5f..50a09fe7 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumPageFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumPageFragment.java @@ -307,7 +307,7 @@ public class AlbumPageFragment extends Fragment implements ClickCallback { } private void observePlayback() { - playbackViewModel.getCurrentMediaId().observe(getViewLifecycleOwner(), id -> { + playbackViewModel.getCurrentSongId().observe(getViewLifecycleOwner(), id -> { if (songHorizontalAdapter != null) { Boolean playing = playbackViewModel.getIsPlaying().getValue(); songHorizontalAdapter.setPlaybackState(id, playing != null && playing); @@ -315,7 +315,7 @@ public class AlbumPageFragment extends Fragment implements ClickCallback { }); playbackViewModel.getIsPlaying().observe(getViewLifecycleOwner(), playing -> { if (songHorizontalAdapter != null) { - String id = playbackViewModel.getCurrentMediaId().getValue(); + String id = playbackViewModel.getCurrentSongId().getValue(); songHorizontalAdapter.setPlaybackState(id, playing != null && playing); } }); @@ -323,7 +323,7 @@ public class AlbumPageFragment extends Fragment implements ClickCallback { private void reapplyPlayback() { if (songHorizontalAdapter != null) { - String id = playbackViewModel.getCurrentMediaId().getValue(); + String id = playbackViewModel.getCurrentSongId().getValue(); Boolean playing = playbackViewModel.getIsPlaying().getValue(); songHorizontalAdapter.setPlaybackState(id, playing != null && playing); } diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/ArtistPageFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/ArtistPageFragment.java index 11b0a210..1d1cb7d1 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/ArtistPageFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/ArtistPageFragment.java @@ -29,20 +29,16 @@ import com.cappielloantonio.tempo.service.MediaManager; import com.cappielloantonio.tempo.service.MediaService; import com.cappielloantonio.tempo.subsonic.models.ArtistID3; import com.cappielloantonio.tempo.ui.activity.MainActivity; -import com.cappielloantonio.tempo.ui.adapter.AlbumArtistPageOrSimilarAdapter; import com.cappielloantonio.tempo.ui.adapter.AlbumCatalogueAdapter; import com.cappielloantonio.tempo.ui.adapter.ArtistCatalogueAdapter; -import com.cappielloantonio.tempo.ui.adapter.ArtistSimilarAdapter; import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter; import com.cappielloantonio.tempo.util.Constants; import com.cappielloantonio.tempo.util.MusicUtil; -import com.cappielloantonio.tempo.util.Preferences; import com.cappielloantonio.tempo.viewmodel.ArtistPageViewModel; import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel; import com.google.common.util.concurrent.ListenableFuture; import java.util.ArrayList; -import java.util.Collections; import java.util.List; @UnstableApi @@ -282,7 +278,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback { } private void observePlayback() { - playbackViewModel.getCurrentMediaId().observe(getViewLifecycleOwner(), id -> { + playbackViewModel.getCurrentSongId().observe(getViewLifecycleOwner(), id -> { if (songHorizontalAdapter != null) { Boolean playing = playbackViewModel.getIsPlaying().getValue(); songHorizontalAdapter.setPlaybackState(id, playing != null && playing); @@ -290,7 +286,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback { }); playbackViewModel.getIsPlaying().observe(getViewLifecycleOwner(), playing -> { if (songHorizontalAdapter != null) { - String id = playbackViewModel.getCurrentMediaId().getValue(); + String id = playbackViewModel.getCurrentSongId().getValue(); songHorizontalAdapter.setPlaybackState(id, playing != null && playing); } }); @@ -298,7 +294,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback { private void reapplyPlayback() { if (songHorizontalAdapter != null) { - String id = playbackViewModel.getCurrentMediaId().getValue(); + String id = playbackViewModel.getCurrentSongId().getValue(); Boolean playing = playbackViewModel.getIsPlaying().getValue(); songHorizontalAdapter.setPlaybackState(id, playing != null && playing); } diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeTabMusicFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeTabMusicFragment.java index 672dabd4..ae95f35f 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeTabMusicFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeTabMusicFragment.java @@ -1058,7 +1058,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback { } private void observeStarredSongsPlayback() { - playbackViewModel.getCurrentMediaId().observe(getViewLifecycleOwner(), id -> { + playbackViewModel.getCurrentSongId().observe(getViewLifecycleOwner(), id -> { if (starredSongAdapter != null) { Boolean playing = playbackViewModel.getIsPlaying().getValue(); starredSongAdapter.setPlaybackState(id, playing != null && playing); @@ -1066,14 +1066,14 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback { }); playbackViewModel.getIsPlaying().observe(getViewLifecycleOwner(), playing -> { if (starredSongAdapter != null) { - String id = playbackViewModel.getCurrentMediaId().getValue(); + String id = playbackViewModel.getCurrentSongId().getValue(); starredSongAdapter.setPlaybackState(id, playing != null && playing); } }); } private void observeTopSongsPlayback() { - playbackViewModel.getCurrentMediaId().observe(getViewLifecycleOwner(), id -> { + playbackViewModel.getCurrentSongId().observe(getViewLifecycleOwner(), id -> { if (topSongAdapter != null) { Boolean playing = playbackViewModel.getIsPlaying().getValue(); topSongAdapter.setPlaybackState(id, playing != null && playing); @@ -1081,7 +1081,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback { }); playbackViewModel.getIsPlaying().observe(getViewLifecycleOwner(), playing -> { if (topSongAdapter != null) { - String id = playbackViewModel.getCurrentMediaId().getValue(); + String id = playbackViewModel.getCurrentSongId().getValue(); topSongAdapter.setPlaybackState(id, playing != null && playing); } }); @@ -1089,7 +1089,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback { private void reapplyStarredSongsPlayback() { if (starredSongAdapter != null) { - String id = playbackViewModel.getCurrentMediaId().getValue(); + String id = playbackViewModel.getCurrentSongId().getValue(); Boolean playing = playbackViewModel.getIsPlaying().getValue(); starredSongAdapter.setPlaybackState(id, playing != null && playing); } @@ -1097,7 +1097,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback { private void reapplyTopSongsPlayback() { if (topSongAdapter != null) { - String id = playbackViewModel.getCurrentMediaId().getValue(); + String id = playbackViewModel.getCurrentSongId().getValue(); Boolean playing = playbackViewModel.getIsPlaying().getValue(); topSongAdapter.setPlaybackState(id, playing != null && playing); } diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerQueueFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerQueueFragment.java index 1686cc21..06536cd6 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerQueueFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerQueueFragment.java @@ -227,7 +227,7 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback { } private void observePlayback() { - playbackViewModel.getCurrentMediaId().observe(getViewLifecycleOwner(), id -> { + playbackViewModel.getCurrentSongId().observe(getViewLifecycleOwner(), id -> { if (playerSongQueueAdapter != null) { Boolean playing = playbackViewModel.getIsPlaying().getValue(); playerSongQueueAdapter.setPlaybackState(id, playing != null && playing); @@ -235,7 +235,7 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback { }); playbackViewModel.getIsPlaying().observe(getViewLifecycleOwner(), playing -> { if (playerSongQueueAdapter != null) { - String id = playbackViewModel.getCurrentMediaId().getValue(); + String id = playbackViewModel.getCurrentSongId().getValue(); playerSongQueueAdapter.setPlaybackState(id, playing != null && playing); } }); @@ -243,7 +243,7 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback { private void reapplyPlayback() { if (playerSongQueueAdapter != null) { - String id = playbackViewModel.getCurrentMediaId().getValue(); + String id = playbackViewModel.getCurrentSongId().getValue(); Boolean playing = playbackViewModel.getIsPlaying().getValue(); playerSongQueueAdapter.setPlaybackState(id, playing != null && playing); } diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlaylistPageFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlaylistPageFragment.java index e51afc56..ef58e96c 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlaylistPageFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlaylistPageFragment.java @@ -282,7 +282,7 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback { } private void observePlayback() { - playbackViewModel.getCurrentMediaId().observe(getViewLifecycleOwner(), id -> { + playbackViewModel.getCurrentSongId().observe(getViewLifecycleOwner(), id -> { if (songHorizontalAdapter != null) { Boolean playing = playbackViewModel.getIsPlaying().getValue(); songHorizontalAdapter.setPlaybackState(id, playing != null && playing); @@ -290,7 +290,7 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback { }); playbackViewModel.getIsPlaying().observe(getViewLifecycleOwner(), playing -> { if (songHorizontalAdapter != null) { - String id = playbackViewModel.getCurrentMediaId().getValue(); + String id = playbackViewModel.getCurrentSongId().getValue(); songHorizontalAdapter.setPlaybackState(id, playing != null && playing); } }); @@ -298,7 +298,7 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback { private void reapplyPlayback() { if (songHorizontalAdapter != null) { - String id = playbackViewModel.getCurrentMediaId().getValue(); + String id = playbackViewModel.getCurrentSongId().getValue(); Boolean playing = playbackViewModel.getIsPlaying().getValue(); songHorizontalAdapter.setPlaybackState(id, playing != null && playing); } diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SearchFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SearchFragment.java index 7136329e..b6c3b8d3 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SearchFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SearchFragment.java @@ -4,14 +4,11 @@ import android.content.ComponentName; import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.view.inputmethod.EditorInfo; import android.widget.ImageView; import android.widget.TextView; -import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -303,7 +300,7 @@ public class SearchFragment extends Fragment implements ClickCallback { } private void observePlayback() { - playbackViewModel.getCurrentMediaId().observe(getViewLifecycleOwner(), id -> { + playbackViewModel.getCurrentSongId().observe(getViewLifecycleOwner(), id -> { if (songHorizontalAdapter != null) { Boolean playing = playbackViewModel.getIsPlaying().getValue(); songHorizontalAdapter.setPlaybackState(id, playing != null && playing); @@ -311,7 +308,7 @@ public class SearchFragment extends Fragment implements ClickCallback { }); playbackViewModel.getIsPlaying().observe(getViewLifecycleOwner(), playing -> { if (songHorizontalAdapter != null) { - String id = playbackViewModel.getCurrentMediaId().getValue(); + String id = playbackViewModel.getCurrentSongId().getValue(); songHorizontalAdapter.setPlaybackState(id, playing != null && playing); } }); @@ -319,7 +316,7 @@ public class SearchFragment extends Fragment implements ClickCallback { private void reapplyPlayback() { if (songHorizontalAdapter != null) { - String id = playbackViewModel.getCurrentMediaId().getValue(); + String id = playbackViewModel.getCurrentSongId().getValue(); Boolean playing = playbackViewModel.getIsPlaying().getValue(); songHorizontalAdapter.setPlaybackState(id, playing != null && playing); } diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SongListPageFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SongListPageFragment.java index db6ebe38..4f8bad46 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SongListPageFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SongListPageFragment.java @@ -335,7 +335,7 @@ public class SongListPageFragment extends Fragment implements ClickCallback { } private void observePlayback() { - playbackViewModel.getCurrentMediaId().observe(getViewLifecycleOwner(), id -> { + playbackViewModel.getCurrentSongId().observe(getViewLifecycleOwner(), id -> { if (songHorizontalAdapter != null) { Boolean playing = playbackViewModel.getIsPlaying().getValue(); songHorizontalAdapter.setPlaybackState(id, playing != null && playing); @@ -343,7 +343,7 @@ public class SongListPageFragment extends Fragment implements ClickCallback { }); playbackViewModel.getIsPlaying().observe(getViewLifecycleOwner(), playing -> { if (songHorizontalAdapter != null) { - String id = playbackViewModel.getCurrentMediaId().getValue(); + String id = playbackViewModel.getCurrentSongId().getValue(); songHorizontalAdapter.setPlaybackState(id, playing != null && playing); } }); @@ -351,7 +351,7 @@ public class SongListPageFragment extends Fragment implements ClickCallback { private void reapplyPlayback() { if (songHorizontalAdapter != null) { - String id = playbackViewModel.getCurrentMediaId().getValue(); + String id = playbackViewModel.getCurrentSongId().getValue(); Boolean playing = playbackViewModel.getIsPlaying().getValue(); songHorizontalAdapter.setPlaybackState(id, playing != null && playing); } diff --git a/app/src/main/java/com/cappielloantonio/tempo/viewmodel/PlaybackViewModel.java b/app/src/main/java/com/cappielloantonio/tempo/viewmodel/PlaybackViewModel.java index 6f266148..b1808d9f 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/viewmodel/PlaybackViewModel.java +++ b/app/src/main/java/com/cappielloantonio/tempo/viewmodel/PlaybackViewModel.java @@ -8,20 +8,20 @@ import java.util.Objects; public class PlaybackViewModel extends ViewModel { - private final MutableLiveData currentMediaId = new MutableLiveData<>(null); + private final MutableLiveData currentSongId = new MutableLiveData<>(null); private final MutableLiveData isPlaying = new MutableLiveData<>(false); - public LiveData getCurrentMediaId() { - return currentMediaId; + public LiveData getCurrentSongId() { + return currentSongId; } public LiveData getIsPlaying() { return isPlaying; } - public void update(String mediaId, boolean playing) { - if (!Objects.equals(currentMediaId.getValue(), mediaId)) { - currentMediaId.postValue(mediaId); + public void update(String songId, boolean playing) { + if (!Objects.equals(currentSongId.getValue(), songId)) { + currentSongId.postValue(songId); } if (!Objects.equals(isPlaying.getValue(), playing)) { isPlaying.postValue(playing); @@ -29,7 +29,7 @@ public class PlaybackViewModel extends ViewModel { } public void clear() { - currentMediaId.postValue(null); + currentSongId.postValue(null); isPlaying.postValue(false); } } \ No newline at end of file From 7ec78991a5feac1f48d33af5f98a6679113d6dbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Garc=C3=ADa?= <55400857+jaime-grj@users.noreply.github.com> Date: Mon, 22 Sep 2025 20:03:02 +0200 Subject: [PATCH 8/9] refactor: Rename methods and variables --- .../tempo/ui/adapter/SongHorizontalAdapter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/SongHorizontalAdapter.java b/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/SongHorizontalAdapter.java index 0a83e0e7..9f606e9a 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/SongHorizontalAdapter.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/SongHorizontalAdapter.java @@ -322,7 +322,8 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter(MusicUtil.limitPlayableMedia(songs, getBindingAdapterPosition()))); - bundle.putInt( Constants.ITEM_POSITION, MusicUtil.getPlayableMediaPosition(songs, getBindingAdapterPosition())); + bundle.putInt(Constants.ITEM_POSITION, MusicUtil.getPlayableMediaPosition(songs, getBindingAdapterPosition())); + click.onMediaClick(bundle); } From cd195dbba0ef54b6fc13bf6b7cc930c53701c91a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Garc=C3=ADa?= <55400857+jaime-grj@users.noreply.github.com> Date: Mon, 22 Sep 2025 20:21:27 +0200 Subject: [PATCH 9/9] refactor: Remove unused import --- .../tempo/ui/adapter/PlayerSongQueueAdapter.java | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/PlayerSongQueueAdapter.java b/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/PlayerSongQueueAdapter.java index e6f25d66..9a35fd92 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/PlayerSongQueueAdapter.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/PlayerSongQueueAdapter.java @@ -1,6 +1,5 @@ package com.cappielloantonio.tempo.ui.adapter; -import android.app.Activity; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.util.Log;