mirror of
https://github.com/antebudimir/tempus.git
synced 2025-12-31 17:43:32 +00:00
feat: Add play/pause button in song lists
This commit is contained in:
parent
905bb3e3c5
commit
5ab68e4a98
14 changed files with 555 additions and 135 deletions
|
|
@ -1,8 +0,0 @@
|
||||||
package com.cappielloantonio.tempo.interfaces;
|
|
||||||
|
|
||||||
import androidx.annotation.Keep;
|
|
||||||
|
|
||||||
@Keep
|
|
||||||
public interface MediaSongIdCallback {
|
|
||||||
default void onRecovery(String id) {}
|
|
||||||
}
|
|
||||||
|
|
@ -2,17 +2,20 @@ package com.cappielloantonio.tempo.service;
|
||||||
|
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.OptIn;
|
import androidx.annotation.OptIn;
|
||||||
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.Observer;
|
import androidx.lifecycle.Observer;
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
|
import androidx.media3.common.Player;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.session.MediaBrowser;
|
import androidx.media3.session.MediaBrowser;
|
||||||
import androidx.media3.session.SessionToken;
|
import androidx.media3.session.SessionToken;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.App;
|
import com.cappielloantonio.tempo.App;
|
||||||
import com.cappielloantonio.tempo.interfaces.MediaIndexCallback;
|
import com.cappielloantonio.tempo.interfaces.MediaIndexCallback;
|
||||||
import com.cappielloantonio.tempo.interfaces.MediaSongIdCallback;
|
|
||||||
import com.cappielloantonio.tempo.model.Chronology;
|
import com.cappielloantonio.tempo.model.Chronology;
|
||||||
import com.cappielloantonio.tempo.repository.ChronologyRepository;
|
import com.cappielloantonio.tempo.repository.ChronologyRepository;
|
||||||
import com.cappielloantonio.tempo.repository.QueueRepository;
|
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.subsonic.models.PodcastEpisode;
|
||||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||||
import com.cappielloantonio.tempo.util.Preferences;
|
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.ListenableFuture;
|
||||||
import com.google.common.util.concurrent.MoreExecutors;
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
public class MediaManager {
|
public class MediaManager {
|
||||||
private static final String TAG = "MediaManager";
|
private static final String TAG = "MediaManager";
|
||||||
|
private static WeakReference<MediaBrowser> 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<MediaBrowser> browserFuture,
|
||||||
|
PlaybackViewModel playbackViewModel
|
||||||
|
) {
|
||||||
|
if (browserFuture == null) return;
|
||||||
|
|
||||||
|
Futures.addCallback(browserFuture, new FutureCallback<MediaBrowser>() {
|
||||||
|
@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<MediaBrowser> mediaBrowserListenableFuture) {
|
public static void reset(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture) {
|
||||||
if (mediaBrowserListenableFuture != null) {
|
if (mediaBrowserListenableFuture != null) {
|
||||||
|
|
@ -293,25 +370,6 @@ public class MediaManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void getCurrentSongId(ListenableFuture<MediaBrowser> 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) {
|
public static void setLastPlayedTimestamp(MediaItem mediaItem) {
|
||||||
if (mediaItem != null) getQueueRepository().setLastPlayedTimestamp(mediaItem.mediaId);
|
if (mediaItem != null) getQueueRepository().setLastPlayedTimestamp(mediaItem.mediaId);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package com.cappielloantonio.tempo.ui.adapter;
|
package com.cappielloantonio.tempo.ui.adapter;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
@ -17,24 +19,30 @@ import com.cappielloantonio.tempo.databinding.ItemPlayerQueueSongBinding;
|
||||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||||
import com.cappielloantonio.tempo.interfaces.MediaIndexCallback;
|
import com.cappielloantonio.tempo.interfaces.MediaIndexCallback;
|
||||||
import com.cappielloantonio.tempo.interfaces.MediaSongIdCallback;
|
|
||||||
import com.cappielloantonio.tempo.service.MediaManager;
|
import com.cappielloantonio.tempo.service.MediaManager;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||||
import com.cappielloantonio.tempo.util.Preferences;
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public class PlayerSongQueueAdapter extends RecyclerView.Adapter<PlayerSongQueueAdapter.ViewHolder> {
|
public class PlayerSongQueueAdapter extends RecyclerView.Adapter<PlayerSongQueueAdapter.ViewHolder> {
|
||||||
|
private static final String TAG = "PlayerSongQueueAdapter";
|
||||||
private final ClickCallback click;
|
private final ClickCallback click;
|
||||||
|
|
||||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||||
private List<Child> songs;
|
private List<Child> songs;
|
||||||
|
|
||||||
|
private String currentPlayingId;
|
||||||
|
private boolean isPlaying;
|
||||||
|
private List<Integer> currentPlayingPositions = Collections.emptyList();
|
||||||
|
|
||||||
public PlayerSongQueueAdapter(ClickCallback click) {
|
public PlayerSongQueueAdapter(ClickCallback click) {
|
||||||
this.click = click;
|
this.click = click;
|
||||||
this.songs = Collections.emptyList();
|
this.songs = Collections.emptyList();
|
||||||
|
|
@ -87,19 +95,6 @@ public class PlayerSongQueueAdapter extends RecyclerView.Adapter<PlayerSongQueue
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
MediaManager.getCurrentSongId(mediaBrowserListenableFuture, new MediaSongIdCallback() {
|
|
||||||
@Override
|
|
||||||
public void onRecovery(String id) {
|
|
||||||
if (song.getId().equals(id)) {
|
|
||||||
holder.item.playPauseIcon.setVisibility(View.VISIBLE);
|
|
||||||
holder.item.coverArtOverlay.setVisibility(View.VISIBLE);
|
|
||||||
} else {
|
|
||||||
holder.item.playPauseIcon.setVisibility(View.INVISIBLE);
|
|
||||||
holder.item.coverArtOverlay.setVisibility(View.INVISIBLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (Preferences.showItemRating()) {
|
if (Preferences.showItemRating()) {
|
||||||
if (song.getStarred() == null && song.getUserRating() == null) {
|
if (song.getStarred() == null && song.getUserRating() == null) {
|
||||||
holder.item.ratingIndicatorImageView.setVisibility(View.GONE);
|
holder.item.ratingIndicatorImageView.setVisibility(View.GONE);
|
||||||
|
|
@ -118,6 +113,50 @@ public class PlayerSongQueueAdapter extends RecyclerView.Adapter<PlayerSongQueue
|
||||||
} else {
|
} else {
|
||||||
holder.item.ratingIndicatorImageView.setVisibility(View.GONE);
|
holder.item.ratingIndicatorImageView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
holder.item.playPauseButton.setOnClickListener(v -> {
|
||||||
|
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<Child> getItems() {
|
public List<Child> getItems() {
|
||||||
|
|
@ -146,6 +185,46 @@ public class PlayerSongQueueAdapter extends RecyclerView.Adapter<PlayerSongQueue
|
||||||
this.mediaBrowserListenableFuture = mediaBrowserListenableFuture;
|
this.mediaBrowserListenableFuture = mediaBrowserListenableFuture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setPlaybackState(String mediaId, boolean playing) {
|
||||||
|
String oldId = this.currentPlayingId;
|
||||||
|
boolean oldPlaying = this.isPlaying;
|
||||||
|
List<Integer> oldPositions = currentPlayingPositions;
|
||||||
|
|
||||||
|
this.currentPlayingId = mediaId;
|
||||||
|
this.isPlaying = playing;
|
||||||
|
|
||||||
|
if (Objects.equals(oldId, mediaId) && oldPlaying == playing) {
|
||||||
|
List<Integer> 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<Integer> findPositionsById(String id) {
|
||||||
|
if (id == null) return Collections.emptyList();
|
||||||
|
List<Integer> 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) {
|
public Child getItem(int id) {
|
||||||
return songs.get(id);
|
return songs.get(id);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package com.cappielloantonio.tempo.ui.adapter;
|
package com.cappielloantonio.tempo.ui.adapter;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
@ -17,8 +19,6 @@ import com.cappielloantonio.tempo.R;
|
||||||
import com.cappielloantonio.tempo.databinding.ItemHorizontalTrackBinding;
|
import com.cappielloantonio.tempo.databinding.ItemHorizontalTrackBinding;
|
||||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
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.AlbumID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.DiscTitle;
|
import com.cappielloantonio.tempo.subsonic.models.DiscTitle;
|
||||||
|
|
@ -45,7 +45,10 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
||||||
private List<Child> songsFull;
|
private List<Child> songsFull;
|
||||||
private List<Child> songs;
|
private List<Child> songs;
|
||||||
private String currentFilter;
|
private String currentFilter;
|
||||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
|
||||||
|
private String currentPlayingId;
|
||||||
|
private boolean isPlaying;
|
||||||
|
private List<Integer> currentPlayingPositions = Collections.emptyList();
|
||||||
|
|
||||||
private final Filter filtering = new Filter() {
|
private final Filter filtering = new Filter() {
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -75,6 +78,12 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
||||||
protected void publishResults(CharSequence constraint, FilterResults results) {
|
protected void publishResults(CharSequence constraint, FilterResults results) {
|
||||||
songs = (List<Child>) results.values;
|
songs = (List<Child>) results.values;
|
||||||
notifyDataSetChanged();
|
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<SongHorizontalAd
|
||||||
this.songsFull = Collections.emptyList();
|
this.songsFull = Collections.emptyList();
|
||||||
this.currentFilter = "";
|
this.currentFilter = "";
|
||||||
this.album = album;
|
this.album = album;
|
||||||
|
setHasStableIds(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
|
@ -96,7 +106,16 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> 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);
|
Child song = songs.get(position);
|
||||||
|
|
||||||
holder.item.searchResultSongTitleTextView.setText(song.getTitle());
|
holder.item.searchResultSongTitleTextView.setText(song.getTitle());
|
||||||
|
|
@ -171,20 +190,46 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
||||||
holder.item.ratingIndicatorImageView.setVisibility(View.GONE);
|
holder.item.ratingIndicatorImageView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaManager.getCurrentSongId(mediaBrowserListenableFuture, new MediaSongIdCallback() {
|
holder.item.playPauseButton.setOnClickListener(v -> {
|
||||||
@Override
|
Activity a = (Activity) v.getContext();
|
||||||
public void onRecovery(String id) {
|
View root = a.findViewById(android.R.id.content);
|
||||||
if (song.getId().equals(id)) {
|
View exoPlayPause = root.findViewById(R.id.exo_play_pause);
|
||||||
holder.item.playPauseIcon.setVisibility(View.VISIBLE);
|
if (exoPlayPause != null) exoPlayPause.performClick();
|
||||||
if (!showCoverArt) holder.item.trackNumberTextView.setVisibility(View.INVISIBLE);
|
});
|
||||||
if (showCoverArt) holder.item.coverArtOverlay.setVisibility(View.VISIBLE);
|
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 {
|
} else {
|
||||||
holder.item.playPauseIcon.setVisibility(View.INVISIBLE);
|
holder.item.coverArtOverlay.setVisibility(View.VISIBLE);
|
||||||
if (showCoverArt) holder.item.coverArtOverlay.setVisibility(View.INVISIBLE);
|
}
|
||||||
if (!showCoverArt) holder.item.trackNumberTextView.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
|
@Override
|
||||||
|
|
@ -195,7 +240,6 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
||||||
public void setItems(List<Child> songs) {
|
public void setItems(List<Child> songs) {
|
||||||
this.songsFull = songs != null ? songs : Collections.emptyList();
|
this.songsFull = songs != null ? songs : Collections.emptyList();
|
||||||
filtering.filter(currentFilter);
|
filtering.filter(currentFilter);
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -208,6 +252,46 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setPlaybackState(String mediaId, boolean playing) {
|
||||||
|
String oldId = this.currentPlayingId;
|
||||||
|
boolean oldPlaying = this.isPlaying;
|
||||||
|
List<Integer> oldPositions = currentPlayingPositions;
|
||||||
|
|
||||||
|
this.currentPlayingId = mediaId;
|
||||||
|
this.isPlaying = playing;
|
||||||
|
|
||||||
|
if (Objects.equals(oldId, mediaId) && oldPlaying == playing) {
|
||||||
|
List<Integer> 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<Integer> findPositionsById(String id) {
|
||||||
|
if (id == null) return Collections.emptyList();
|
||||||
|
List<Integer> positions = new ArrayList<>();
|
||||||
|
for (int i = 0; i < songs.size(); i++) {
|
||||||
|
if (id.equals(songs.get(i).getId())) {
|
||||||
|
positions.add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return positions;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Filter getFilter() {
|
public Filter getFilter() {
|
||||||
return filtering;
|
return filtering;
|
||||||
|
|
@ -236,18 +320,21 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
||||||
|
|
||||||
public void onClick() {
|
public void onClick() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(MusicUtil.limitPlayableMedia(songs, getBindingAdapterPosition())));
|
bundle.putParcelableArrayList(
|
||||||
bundle.putInt(Constants.ITEM_POSITION, MusicUtil.getPlayableMediaPosition(songs, getBindingAdapterPosition()));
|
Constants.TRACKS_OBJECT,
|
||||||
|
new ArrayList<>(MusicUtil.limitPlayableMedia(songs, getBindingAdapterPosition()))
|
||||||
|
);
|
||||||
|
bundle.putInt(
|
||||||
|
Constants.ITEM_POSITION,
|
||||||
|
MusicUtil.getPlayableMediaPosition(songs, getBindingAdapterPosition())
|
||||||
|
);
|
||||||
click.onMediaClick(bundle);
|
click.onMediaClick(bundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean onLongClick() {
|
private boolean onLongClick() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putParcelable(Constants.TRACK_OBJECT, songs.get(getBindingAdapterPosition()));
|
bundle.putParcelable(Constants.TRACK_OBJECT, songs.get(getBindingAdapterPosition()));
|
||||||
|
|
||||||
click.onMediaLongClick(bundle);
|
click.onMediaLongClick(bundle);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -267,8 +354,4 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
||||||
|
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMediaBrowserListenableFuture(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture) {
|
|
||||||
this.mediaBrowserListenableFuture = mediaBrowserListenableFuture;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||||
import com.cappielloantonio.tempo.viewmodel.AlbumPageViewModel;
|
import com.cappielloantonio.tempo.viewmodel.AlbumPageViewModel;
|
||||||
|
import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -52,6 +53,7 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
|
||||||
private FragmentAlbumPageBinding bind;
|
private FragmentAlbumPageBinding bind;
|
||||||
private MainActivity activity;
|
private MainActivity activity;
|
||||||
private AlbumPageViewModel albumPageViewModel;
|
private AlbumPageViewModel albumPageViewModel;
|
||||||
|
private PlaybackViewModel playbackViewModel;
|
||||||
private SongHorizontalAdapter songHorizontalAdapter;
|
private SongHorizontalAdapter songHorizontalAdapter;
|
||||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||||
|
|
||||||
|
|
@ -74,6 +76,7 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
|
||||||
bind = FragmentAlbumPageBinding.inflate(inflater, container, false);
|
bind = FragmentAlbumPageBinding.inflate(inflater, container, false);
|
||||||
View view = bind.getRoot();
|
View view = bind.getRoot();
|
||||||
albumPageViewModel = new ViewModelProvider(requireActivity()).get(AlbumPageViewModel.class);
|
albumPageViewModel = new ViewModelProvider(requireActivity()).get(AlbumPageViewModel.class);
|
||||||
|
playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class);
|
||||||
|
|
||||||
init();
|
init();
|
||||||
initAppBar();
|
initAppBar();
|
||||||
|
|
@ -91,12 +94,9 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
|
|
||||||
initializeMediaBrowser();
|
initializeMediaBrowser();
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
MediaManager.registerPlaybackObserver(getViewLifecycleOwner(), mediaBrowserListenableFuture, playbackViewModel);
|
||||||
public void onResume() {
|
observePlayback();
|
||||||
super.onResume();
|
|
||||||
setMediaBrowserListenableFuture();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -277,9 +277,12 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
|
||||||
|
|
||||||
songHorizontalAdapter = new SongHorizontalAdapter(this, false, false, album);
|
songHorizontalAdapter = new SongHorizontalAdapter(this, false, false, album);
|
||||||
bind.songRecyclerView.setAdapter(songHorizontalAdapter);
|
bind.songRecyclerView.setAdapter(songHorizontalAdapter);
|
||||||
setMediaBrowserListenableFuture();
|
reapplyPlayback();
|
||||||
|
|
||||||
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
|
@Override
|
||||||
public void onMediaClick(Bundle bundle) {
|
public void onMediaClick(Bundle bundle) {
|
||||||
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
|
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
|
||||||
songHorizontalAdapter.notifyDataSetChanged();
|
|
||||||
activity.setBottomSheetInPeek(true);
|
activity.setBottomSheetInPeek(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -304,9 +306,26 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
|
||||||
Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle);
|
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) {
|
if (songHorizontalAdapter != null) {
|
||||||
songHorizontalAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
|
String id = playbackViewModel.getCurrentMediaId().getValue();
|
||||||
|
Boolean playing = playbackViewModel.getIsPlaying().getValue();
|
||||||
|
songHorizontalAdapter.setPlaybackState(id, playing != null && playing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -38,6 +38,7 @@ import com.cappielloantonio.tempo.util.Constants;
|
||||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||||
import com.cappielloantonio.tempo.util.Preferences;
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
import com.cappielloantonio.tempo.viewmodel.ArtistPageViewModel;
|
import com.cappielloantonio.tempo.viewmodel.ArtistPageViewModel;
|
||||||
|
import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -49,6 +50,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||||
private FragmentArtistPageBinding bind;
|
private FragmentArtistPageBinding bind;
|
||||||
private MainActivity activity;
|
private MainActivity activity;
|
||||||
private ArtistPageViewModel artistPageViewModel;
|
private ArtistPageViewModel artistPageViewModel;
|
||||||
|
private PlaybackViewModel playbackViewModel;
|
||||||
|
|
||||||
private SongHorizontalAdapter songHorizontalAdapter;
|
private SongHorizontalAdapter songHorizontalAdapter;
|
||||||
private AlbumCatalogueAdapter albumCatalogueAdapter;
|
private AlbumCatalogueAdapter albumCatalogueAdapter;
|
||||||
|
|
@ -63,6 +65,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||||
bind = FragmentArtistPageBinding.inflate(inflater, container, false);
|
bind = FragmentArtistPageBinding.inflate(inflater, container, false);
|
||||||
View view = bind.getRoot();
|
View view = bind.getRoot();
|
||||||
artistPageViewModel = new ViewModelProvider(requireActivity()).get(ArtistPageViewModel.class);
|
artistPageViewModel = new ViewModelProvider(requireActivity()).get(ArtistPageViewModel.class);
|
||||||
|
playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class);
|
||||||
|
|
||||||
init();
|
init();
|
||||||
initAppBar();
|
initAppBar();
|
||||||
|
|
@ -80,12 +83,8 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
|
|
||||||
initializeMediaBrowser();
|
initializeMediaBrowser();
|
||||||
}
|
MediaManager.registerPlaybackObserver(getViewLifecycleOwner(), mediaBrowserListenableFuture, playbackViewModel);
|
||||||
|
observePlayback();
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
setMediaBrowserListenableFuture();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -180,7 +179,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||||
|
|
||||||
songHorizontalAdapter = new SongHorizontalAdapter(this, true, true, null);
|
songHorizontalAdapter = new SongHorizontalAdapter(this, true, true, null);
|
||||||
bind.mostStreamedSongRecyclerView.setAdapter(songHorizontalAdapter);
|
bind.mostStreamedSongRecyclerView.setAdapter(songHorizontalAdapter);
|
||||||
setMediaBrowserListenableFuture();
|
reapplyPlayback();
|
||||||
artistPageViewModel.getArtistTopSongList().observe(getViewLifecycleOwner(), songs -> {
|
artistPageViewModel.getArtistTopSongList().observe(getViewLifecycleOwner(), songs -> {
|
||||||
if (songs == null) {
|
if (songs == null) {
|
||||||
if (bind != null) bind.artistPageTopSongsSector.setVisibility(View.GONE);
|
if (bind != null) bind.artistPageTopSongsSector.setVisibility(View.GONE);
|
||||||
|
|
@ -190,6 +189,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||||
if (bind != null)
|
if (bind != null)
|
||||||
bind.artistPageShuffleButton.setEnabled(!songs.isEmpty());
|
bind.artistPageShuffleButton.setEnabled(!songs.isEmpty());
|
||||||
songHorizontalAdapter.setItems(songs);
|
songHorizontalAdapter.setItems(songs);
|
||||||
|
reapplyPlayback();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -253,7 +253,6 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||||
@Override
|
@Override
|
||||||
public void onMediaClick(Bundle bundle) {
|
public void onMediaClick(Bundle bundle) {
|
||||||
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
|
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
|
||||||
songHorizontalAdapter.notifyDataSetChanged();
|
|
||||||
activity.setBottomSheetInPeek(true);
|
activity.setBottomSheetInPeek(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -282,9 +281,26 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||||
Navigation.findNavController(requireView()).navigate(R.id.artistBottomSheetDialog, bundle);
|
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) {
|
if (songHorizontalAdapter != null) {
|
||||||
songHorizontalAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
|
String id = playbackViewModel.getCurrentMediaId().getValue();
|
||||||
|
Boolean playing = playbackViewModel.getIsPlaying().getValue();
|
||||||
|
songHorizontalAdapter.setPlaybackState(id, playing != null && playing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -60,6 +60,7 @@ import com.cappielloantonio.tempo.util.MusicUtil;
|
||||||
import com.cappielloantonio.tempo.util.Preferences;
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
import com.cappielloantonio.tempo.util.UIUtil;
|
import com.cappielloantonio.tempo.util.UIUtil;
|
||||||
import com.cappielloantonio.tempo.viewmodel.HomeViewModel;
|
import com.cappielloantonio.tempo.viewmodel.HomeViewModel;
|
||||||
|
import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel;
|
||||||
import com.google.android.material.snackbar.Snackbar;
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
|
|
@ -74,6 +75,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||||
private FragmentHomeTabMusicBinding bind;
|
private FragmentHomeTabMusicBinding bind;
|
||||||
private MainActivity activity;
|
private MainActivity activity;
|
||||||
private HomeViewModel homeViewModel;
|
private HomeViewModel homeViewModel;
|
||||||
|
private PlaybackViewModel playbackViewModel;
|
||||||
|
|
||||||
private DiscoverSongAdapter discoverSongAdapter;
|
private DiscoverSongAdapter discoverSongAdapter;
|
||||||
private SimilarTrackAdapter similarMusicAdapter;
|
private SimilarTrackAdapter similarMusicAdapter;
|
||||||
|
|
@ -101,6 +103,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||||
bind = FragmentHomeTabMusicBinding.inflate(inflater, container, false);
|
bind = FragmentHomeTabMusicBinding.inflate(inflater, container, false);
|
||||||
View view = bind.getRoot();
|
View view = bind.getRoot();
|
||||||
homeViewModel = new ViewModelProvider(requireActivity()).get(HomeViewModel.class);
|
homeViewModel = new ViewModelProvider(requireActivity()).get(HomeViewModel.class);
|
||||||
|
playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class);
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
|
||||||
|
|
@ -138,14 +141,16 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
|
|
||||||
initializeMediaBrowser();
|
initializeMediaBrowser();
|
||||||
|
|
||||||
|
MediaManager.registerPlaybackObserver(getViewLifecycleOwner(), mediaBrowserListenableFuture, playbackViewModel);
|
||||||
|
observeStarredSongsPlayback();
|
||||||
|
observeTopSongsPlayback();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
refreshSharesView();
|
refreshSharesView();
|
||||||
setTopSongMediaBrowserListenableFuture();
|
|
||||||
setStarredSongMediaBrowserListenableFuture();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -479,7 +484,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||||
|
|
||||||
topSongAdapter = new SongHorizontalAdapter(this, true, false, null);
|
topSongAdapter = new SongHorizontalAdapter(this, true, false, null);
|
||||||
bind.topSongsRecyclerView.setAdapter(topSongAdapter);
|
bind.topSongsRecyclerView.setAdapter(topSongAdapter);
|
||||||
setTopSongMediaBrowserListenableFuture();
|
reapplyTopSongsPlayback();
|
||||||
homeViewModel.getChronologySample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), chronologies -> {
|
homeViewModel.getChronologySample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), chronologies -> {
|
||||||
if (chronologies == null || chronologies.isEmpty()) {
|
if (chronologies == null || chronologies.isEmpty()) {
|
||||||
if (bind != null) bind.homeGridTracksSector.setVisibility(View.GONE);
|
if (bind != null) bind.homeGridTracksSector.setVisibility(View.GONE);
|
||||||
|
|
@ -495,6 +500,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
topSongAdapter.setItems(topSongs);
|
topSongAdapter.setItems(topSongs);
|
||||||
|
reapplyTopSongsPlayback();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -518,7 +524,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||||
|
|
||||||
starredSongAdapter = new SongHorizontalAdapter(this, true, false, null);
|
starredSongAdapter = new SongHorizontalAdapter(this, true, false, null);
|
||||||
bind.starredTracksRecyclerView.setAdapter(starredSongAdapter);
|
bind.starredTracksRecyclerView.setAdapter(starredSongAdapter);
|
||||||
setStarredSongMediaBrowserListenableFuture();
|
reapplyStarredSongsPlayback();
|
||||||
homeViewModel.getStarredTracks(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
|
homeViewModel.getStarredTracks(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
|
||||||
if (songs == null) {
|
if (songs == null) {
|
||||||
if (bind != null) bind.starredTracksSector.setVisibility(View.GONE);
|
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));
|
bind.starredTracksRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(songs.size(), 5), GridLayoutManager.HORIZONTAL, false));
|
||||||
|
|
||||||
starredSongAdapter.setItems(songs);
|
starredSongAdapter.setItems(songs);
|
||||||
|
reapplyStarredSongsPlayback();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -1050,15 +1057,49 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||||
Navigation.findNavController(requireView()).navigate(R.id.shareBottomSheetDialog, bundle);
|
Navigation.findNavController(requireView()).navigate(R.id.shareBottomSheetDialog, bundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setTopSongMediaBrowserListenableFuture() {
|
private void observeStarredSongsPlayback() {
|
||||||
if (topSongAdapter != null) {
|
playbackViewModel.getCurrentMediaId().observe(getViewLifecycleOwner(), id -> {
|
||||||
topSongAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
|
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() {
|
private void reapplyTopSongsPlayback() {
|
||||||
if (starredSongAdapter != null) {
|
if (topSongAdapter != null) {
|
||||||
starredSongAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
|
String id = playbackViewModel.getCurrentMediaId().getValue();
|
||||||
|
Boolean playing = playbackViewModel.getIsPlaying().getValue();
|
||||||
|
topSongAdapter.setPlaybackState(id, playing != null && playing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import com.cappielloantonio.tempo.service.MediaService;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
import com.cappielloantonio.tempo.ui.adapter.PlayerSongQueueAdapter;
|
import com.cappielloantonio.tempo.ui.adapter.PlayerSongQueueAdapter;
|
||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
|
import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel;
|
||||||
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
|
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.common.util.concurrent.MoreExecutors;
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
|
|
@ -38,6 +39,7 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
|
||||||
private InnerFragmentPlayerQueueBinding bind;
|
private InnerFragmentPlayerQueueBinding bind;
|
||||||
|
|
||||||
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
|
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
|
||||||
|
private PlaybackViewModel playbackViewModel;
|
||||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||||
|
|
||||||
private PlayerSongQueueAdapter playerSongQueueAdapter;
|
private PlayerSongQueueAdapter playerSongQueueAdapter;
|
||||||
|
|
@ -48,6 +50,7 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
|
||||||
View view = bind.getRoot();
|
View view = bind.getRoot();
|
||||||
|
|
||||||
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
|
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
|
||||||
|
playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class);
|
||||||
|
|
||||||
initQueueRecyclerView();
|
initQueueRecyclerView();
|
||||||
|
|
||||||
|
|
@ -59,13 +62,15 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
initializeBrowser();
|
initializeBrowser();
|
||||||
bindMediaController();
|
bindMediaController();
|
||||||
|
|
||||||
|
MediaManager.registerPlaybackObserver(getViewLifecycleOwner(), mediaBrowserListenableFuture, playbackViewModel);
|
||||||
|
observePlayback();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
setMediaBrowserListenableFuture();
|
setMediaBrowserListenableFuture();
|
||||||
updateNowPlayingItem();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -110,10 +115,12 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
|
||||||
|
|
||||||
playerSongQueueAdapter = new PlayerSongQueueAdapter(this);
|
playerSongQueueAdapter = new PlayerSongQueueAdapter(this);
|
||||||
bind.playerQueueRecyclerView.setAdapter(playerSongQueueAdapter);
|
bind.playerQueueRecyclerView.setAdapter(playerSongQueueAdapter);
|
||||||
setMediaBrowserListenableFuture();
|
reapplyPlayback();
|
||||||
|
|
||||||
playerBottomSheetViewModel.getQueueSong().observe(getViewLifecycleOwner(), queue -> {
|
playerBottomSheetViewModel.getQueueSong().observe(getViewLifecycleOwner(), queue -> {
|
||||||
if (queue != null) {
|
if (queue != null) {
|
||||||
playerSongQueueAdapter.setItems(queue.stream().map(item -> (Child) item).collect(Collectors.toList()));
|
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
|
@Override
|
||||||
public void onMediaClick(Bundle bundle) {
|
public void onMediaClick(Bundle bundle) {
|
||||||
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -37,6 +37,7 @@ import com.cappielloantonio.tempo.util.Constants;
|
||||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||||
|
import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel;
|
||||||
import com.cappielloantonio.tempo.viewmodel.PlaylistPageViewModel;
|
import com.cappielloantonio.tempo.viewmodel.PlaylistPageViewModel;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
|
|
@ -49,6 +50,7 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
|
||||||
private FragmentPlaylistPageBinding bind;
|
private FragmentPlaylistPageBinding bind;
|
||||||
private MainActivity activity;
|
private MainActivity activity;
|
||||||
private PlaylistPageViewModel playlistPageViewModel;
|
private PlaylistPageViewModel playlistPageViewModel;
|
||||||
|
private PlaybackViewModel playbackViewModel;
|
||||||
|
|
||||||
private SongHorizontalAdapter songHorizontalAdapter;
|
private SongHorizontalAdapter songHorizontalAdapter;
|
||||||
|
|
||||||
|
|
@ -94,6 +96,7 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
|
||||||
bind = FragmentPlaylistPageBinding.inflate(inflater, container, false);
|
bind = FragmentPlaylistPageBinding.inflate(inflater, container, false);
|
||||||
View view = bind.getRoot();
|
View view = bind.getRoot();
|
||||||
playlistPageViewModel = new ViewModelProvider(requireActivity()).get(PlaylistPageViewModel.class);
|
playlistPageViewModel = new ViewModelProvider(requireActivity()).get(PlaylistPageViewModel.class);
|
||||||
|
playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class);
|
||||||
|
|
||||||
init();
|
init();
|
||||||
initAppBar();
|
initAppBar();
|
||||||
|
|
@ -109,12 +112,9 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
|
|
||||||
initializeMediaBrowser();
|
initializeMediaBrowser();
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
MediaManager.registerPlaybackObserver(getViewLifecycleOwner(), mediaBrowserListenableFuture, playbackViewModel);
|
||||||
public void onResume() {
|
observePlayback();
|
||||||
super.onResume();
|
|
||||||
setMediaBrowserListenableFuture();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -254,9 +254,12 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
|
||||||
|
|
||||||
songHorizontalAdapter = new SongHorizontalAdapter(this, true, false, null);
|
songHorizontalAdapter = new SongHorizontalAdapter(this, true, false, null);
|
||||||
bind.songRecyclerView.setAdapter(songHorizontalAdapter);
|
bind.songRecyclerView.setAdapter(songHorizontalAdapter);
|
||||||
setMediaBrowserListenableFuture();
|
reapplyPlayback();
|
||||||
|
|
||||||
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> songHorizontalAdapter.setItems(songs));
|
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> {
|
||||||
|
songHorizontalAdapter.setItems(songs);
|
||||||
|
reapplyPlayback();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeMediaBrowser() {
|
private void initializeMediaBrowser() {
|
||||||
|
|
@ -270,7 +273,6 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
|
||||||
@Override
|
@Override
|
||||||
public void onMediaClick(Bundle bundle) {
|
public void onMediaClick(Bundle bundle) {
|
||||||
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
|
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
|
||||||
songHorizontalAdapter.notifyDataSetChanged();
|
|
||||||
activity.setBottomSheetInPeek(true);
|
activity.setBottomSheetInPeek(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -279,9 +281,26 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
|
||||||
Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle);
|
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) {
|
if (songHorizontalAdapter != null) {
|
||||||
songHorizontalAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
|
String id = playbackViewModel.getCurrentMediaId().getValue();
|
||||||
|
Boolean playing = playbackViewModel.getIsPlaying().getValue();
|
||||||
|
songHorizontalAdapter.setPlaybackState(id, playing != null && playing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter;
|
||||||
import com.cappielloantonio.tempo.ui.adapter.ArtistAdapter;
|
import com.cappielloantonio.tempo.ui.adapter.ArtistAdapter;
|
||||||
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
|
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
|
||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
|
import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel;
|
||||||
import com.cappielloantonio.tempo.viewmodel.SearchViewModel;
|
import com.cappielloantonio.tempo.viewmodel.SearchViewModel;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
|
|
@ -46,6 +47,7 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
||||||
private FragmentSearchBinding bind;
|
private FragmentSearchBinding bind;
|
||||||
private MainActivity activity;
|
private MainActivity activity;
|
||||||
private SearchViewModel searchViewModel;
|
private SearchViewModel searchViewModel;
|
||||||
|
private PlaybackViewModel playbackViewModel;
|
||||||
|
|
||||||
private ArtistAdapter artistAdapter;
|
private ArtistAdapter artistAdapter;
|
||||||
private AlbumAdapter albumAdapter;
|
private AlbumAdapter albumAdapter;
|
||||||
|
|
@ -61,6 +63,7 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
||||||
bind = FragmentSearchBinding.inflate(inflater, container, false);
|
bind = FragmentSearchBinding.inflate(inflater, container, false);
|
||||||
View view = bind.getRoot();
|
View view = bind.getRoot();
|
||||||
searchViewModel = new ViewModelProvider(requireActivity()).get(SearchViewModel.class);
|
searchViewModel = new ViewModelProvider(requireActivity()).get(SearchViewModel.class);
|
||||||
|
playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class);
|
||||||
|
|
||||||
initSearchResultView();
|
initSearchResultView();
|
||||||
initSearchView();
|
initSearchView();
|
||||||
|
|
@ -73,12 +76,14 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
initializeMediaBrowser();
|
initializeMediaBrowser();
|
||||||
|
|
||||||
|
MediaManager.registerPlaybackObserver(getViewLifecycleOwner(), mediaBrowserListenableFuture, playbackViewModel);
|
||||||
|
observePlayback();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
setMediaBrowserListenableFuture();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -119,7 +124,8 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
||||||
bind.searchResultTracksRecyclerView.setHasFixedSize(true);
|
bind.searchResultTracksRecyclerView.setHasFixedSize(true);
|
||||||
|
|
||||||
songHorizontalAdapter = new SongHorizontalAdapter(this, true, false, null);
|
songHorizontalAdapter = new SongHorizontalAdapter(this, true, false, null);
|
||||||
setMediaBrowserListenableFuture();
|
reapplyPlayback();
|
||||||
|
|
||||||
bind.searchResultTracksRecyclerView.setAdapter(songHorizontalAdapter);
|
bind.searchResultTracksRecyclerView.setAdapter(songHorizontalAdapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -296,9 +302,26 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
||||||
Navigation.findNavController(requireView()).navigate(R.id.artistBottomSheetDialog, bundle);
|
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) {
|
if (songHorizontalAdapter != null) {
|
||||||
songHorizontalAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
|
String id = playbackViewModel.getCurrentMediaId().getValue();
|
||||||
|
Boolean playing = playbackViewModel.getIsPlaying().getValue();
|
||||||
|
songHorizontalAdapter.setPlaybackState(id, playing != null && playing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||||
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
|
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
|
||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
|
import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel;
|
||||||
import com.cappielloantonio.tempo.viewmodel.SongListPageViewModel;
|
import com.cappielloantonio.tempo.viewmodel.SongListPageViewModel;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
|
|
@ -49,6 +50,7 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
|
||||||
private FragmentSongListPageBinding bind;
|
private FragmentSongListPageBinding bind;
|
||||||
private MainActivity activity;
|
private MainActivity activity;
|
||||||
private SongListPageViewModel songListPageViewModel;
|
private SongListPageViewModel songListPageViewModel;
|
||||||
|
private PlaybackViewModel playbackViewModel;
|
||||||
|
|
||||||
private SongHorizontalAdapter songHorizontalAdapter;
|
private SongHorizontalAdapter songHorizontalAdapter;
|
||||||
|
|
||||||
|
|
@ -69,6 +71,7 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
|
||||||
bind = FragmentSongListPageBinding.inflate(inflater, container, false);
|
bind = FragmentSongListPageBinding.inflate(inflater, container, false);
|
||||||
View view = bind.getRoot();
|
View view = bind.getRoot();
|
||||||
songListPageViewModel = new ViewModelProvider(requireActivity()).get(SongListPageViewModel.class);
|
songListPageViewModel = new ViewModelProvider(requireActivity()).get(SongListPageViewModel.class);
|
||||||
|
playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class);
|
||||||
|
|
||||||
init();
|
init();
|
||||||
initAppBar();
|
initAppBar();
|
||||||
|
|
@ -82,12 +85,9 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
initializeMediaBrowser();
|
initializeMediaBrowser();
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
MediaManager.registerPlaybackObserver(getViewLifecycleOwner(), mediaBrowserListenableFuture, playbackViewModel);
|
||||||
public void onResume() {
|
observePlayback();
|
||||||
super.onResume();
|
|
||||||
setMediaBrowserListenableFuture();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -197,10 +197,11 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
|
||||||
|
|
||||||
songHorizontalAdapter = new SongHorizontalAdapter(this, true, false, null);
|
songHorizontalAdapter = new SongHorizontalAdapter(this, true, false, null);
|
||||||
bind.songListRecyclerView.setAdapter(songHorizontalAdapter);
|
bind.songListRecyclerView.setAdapter(songHorizontalAdapter);
|
||||||
setMediaBrowserListenableFuture();
|
reapplyPlayback();
|
||||||
songListPageViewModel.getSongList().observe(getViewLifecycleOwner(), songs -> {
|
songListPageViewModel.getSongList().observe(getViewLifecycleOwner(), songs -> {
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
songHorizontalAdapter.setItems(songs);
|
songHorizontalAdapter.setItems(songs);
|
||||||
|
reapplyPlayback();
|
||||||
setSongListPageSubtitle(songs);
|
setSongListPageSubtitle(songs);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -325,7 +326,6 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
|
||||||
public void onMediaClick(Bundle bundle) {
|
public void onMediaClick(Bundle bundle) {
|
||||||
hideKeyboard(requireView());
|
hideKeyboard(requireView());
|
||||||
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
|
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
|
||||||
songHorizontalAdapter.notifyDataSetChanged();
|
|
||||||
activity.setBottomSheetInPeek(true);
|
activity.setBottomSheetInPeek(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -334,9 +334,26 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
|
||||||
Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle);
|
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) {
|
if (songHorizontalAdapter != null) {
|
||||||
songHorizontalAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
|
String id = playbackViewModel.getCurrentMediaId().getValue();
|
||||||
|
Boolean playing = playbackViewModel.getIsPlaying().getValue();
|
||||||
|
songHorizontalAdapter.setPlaybackState(id, playing != null && playing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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<String> currentMediaId = new MutableLiveData<>(null);
|
||||||
|
private final MutableLiveData<Boolean> isPlaying = new MutableLiveData<>(false);
|
||||||
|
|
||||||
|
// (Optional) expose position or other info
|
||||||
|
// private final MutableLiveData<Long> positionMs = new MutableLiveData<>(0L);
|
||||||
|
|
||||||
|
public LiveData<String> getCurrentMediaId() {
|
||||||
|
return currentMediaId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Boolean> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -66,16 +66,21 @@
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/different_disk_divider_sector" />
|
app:layout_constraintTop_toBottomOf="@+id/different_disk_divider_sector" />
|
||||||
|
|
||||||
<ImageView
|
<ToggleButton
|
||||||
android:id="@+id/play_pause_icon"
|
android:id="@+id/play_pause_button"
|
||||||
android:layout_width="28dp"
|
android:layout_width="28dp"
|
||||||
android:layout_height="28dp"
|
android:layout_height="28dp"
|
||||||
android:layout_marginStart="28dp"
|
android:layout_marginStart="28dp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
android:background="@drawable/button_play_pause_selector"
|
||||||
|
android:checked="false"
|
||||||
|
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||||
|
android:text=""
|
||||||
|
android:textOff=""
|
||||||
|
android:textOn=""
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/different_disk_divider_sector"
|
app:layout_constraintTop_toBottomOf="@+id/different_disk_divider_sector" />
|
||||||
android:src="@drawable/ic_play" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/track_number_text_view"
|
android:id="@+id/track_number_text_view"
|
||||||
|
|
|
||||||
|
|
@ -31,16 +31,21 @@
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<ImageView
|
<ToggleButton
|
||||||
android:id="@+id/play_pause_icon"
|
android:id="@+id/play_pause_button"
|
||||||
android:layout_width="28dp"
|
android:layout_width="28dp"
|
||||||
android:layout_height="28dp"
|
android:layout_height="28dp"
|
||||||
android:layout_marginStart="14dp"
|
android:layout_marginStart="14dp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
android:background="@drawable/button_play_pause_selector"
|
||||||
|
android:checked="false"
|
||||||
|
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||||
|
android:text=""
|
||||||
|
android:textOff=""
|
||||||
|
android:textOn=""
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
android:src="@drawable/ic_play" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/queue_song_title_text_view"
|
android:id="@+id/queue_song_title_text_view"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue