Refactored and isolated all of the gross metadata and album art handling

code from MainActivity into MainMetadataView.
This commit is contained in:
casey langen 2017-05-19 19:00:10 -07:00
parent 9cf6cffef5
commit 3f36c8e61b
6 changed files with 558 additions and 445 deletions

View File

@ -3,36 +3,17 @@ package io.casey.musikcube.remote;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.text.SpannableStringBuilder;
import android.text.TextPaint;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.text.style.ForegroundColorSpan;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewPropertyAnimator;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.resource.drawable.GlideDrawable;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.Target;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.Map;
import io.casey.musikcube.remote.playback.Metadata;
import io.casey.musikcube.remote.playback.PlaybackService;
import io.casey.musikcube.remote.playback.PlaybackState;
import io.casey.musikcube.remote.playback.RepeatMode;
@ -43,10 +24,9 @@ import io.casey.musikcube.remote.ui.activity.SettingsActivity;
import io.casey.musikcube.remote.ui.activity.TrackListActivity;
import io.casey.musikcube.remote.ui.activity.WebSocketActivityBase;
import io.casey.musikcube.remote.ui.fragment.InvalidPasswordDialogFragment;
import io.casey.musikcube.remote.ui.model.AlbumArtModel;
import io.casey.musikcube.remote.ui.util.Views;
import io.casey.musikcube.remote.ui.view.LongPressTextView;
import io.casey.musikcube.remote.util.Strings;
import io.casey.musikcube.remote.ui.view.MainMetadataView;
import io.casey.musikcube.remote.websocket.Messages;
import io.casey.musikcube.remote.websocket.Prefs;
import io.casey.musikcube.remote.websocket.SocketMessage;
@ -59,25 +39,12 @@ public class MainActivity extends WebSocketActivityBase {
private SharedPreferences prefs;
private PlaybackService playback;
private Handler handler = new Handler();
private TextView title, artist, album, playPause, volume;
private TextView titleWithArt, artistAndAlbumWithArt, volumeWithArt;
private View mainTrackMetadataWithAlbumArt, mainTrackMetadataNoAlbumArt;
private TextView notPlayingOrDisconnected;
private View buffering, bufferingWithArt;
private View connected;
private MainMetadataView metadataView;
private TextView playPause;
private View connectedNotPlaying, disconnectedButton;
private CheckBox shuffleCb, muteCb, repeatCb;
private View disconnectedOverlay;
private ImageView albumArtImageView;
private ViewPropertyAnimator metadataAnim1, metadataAnim2;
/* ugh, artwork related */
private enum DisplayMode { Artwork, NoArtwork, Stopped }
private AlbumArtModel albumArtModel = AlbumArtModel.empty();
private DisplayMode lastDisplayMode = DisplayMode.Stopped;
private String lastArtworkUrl = null;
static {
REPEAT_TO_STRING_ID = new HashMap<>();
@ -110,6 +77,7 @@ public class MainActivity extends WebSocketActivityBase {
@Override
protected void onPause() {
super.onPause();
metadataView.onPause();
unbindCheckboxEventListeners();
}
@ -117,6 +85,7 @@ public class MainActivity extends WebSocketActivityBase {
protected void onResume() {
super.onResume();
this.playback = getPlaybackService();
metadataView.onResume(playback);
bindCheckBoxEventListeners();
rebindUi();
}
@ -185,32 +154,14 @@ public class MainActivity extends WebSocketActivityBase {
}
private void bindEventListeners() {
this.title = (TextView) findViewById(R.id.track_title);
this.artist = (TextView) findViewById(R.id.track_artist);
this.album = (TextView) findViewById(R.id.track_album);
this.volume = (TextView) findViewById(R.id.volume);
this.buffering = findViewById(R.id.buffering);
this.titleWithArt = (TextView) findViewById(R.id.with_art_track_title);
this.artistAndAlbumWithArt = (TextView) findViewById(R.id.with_art_artist_and_album);
this.volumeWithArt = (TextView) findViewById(R.id.with_art_volume);
this.bufferingWithArt = findViewById(R.id.with_art_buffering);
this.playPause = (TextView) findViewById(R.id.button_play_pause);
this.metadataView = (MainMetadataView) findViewById(R.id.main_metadata_view);
this.shuffleCb = (CheckBox) findViewById(R.id.check_shuffle);
this.muteCb = (CheckBox) findViewById(R.id.check_mute);
this.repeatCb = (CheckBox) findViewById(R.id.check_repeat);
this.mainTrackMetadataWithAlbumArt = findViewById(R.id.main_track_metadata_with_art);
this.mainTrackMetadataNoAlbumArt = findViewById(R.id.main_track_metadata_without_art);
this.notPlayingOrDisconnected = (TextView) findViewById(R.id.main_not_playing);
this.albumArtImageView = (ImageView) findViewById(R.id.album_art);
this.connected = findViewById(R.id.connected);
this.connectedNotPlaying = findViewById(R.id.connected_not_playing);
this.disconnectedButton = findViewById(R.id.disconnected);
this.disconnectedOverlay = findViewById(R.id.disconnected_overlay);
/* these will get faded in as appropriate */
this.mainTrackMetadataNoAlbumArt.setAlpha(0.0f);
this.mainTrackMetadataWithAlbumArt.setAlpha(0.0f);
this.playPause = (TextView) findViewById(R.id.button_play_pause);
findViewById(R.id.button_prev).setOnClickListener((View view) -> playback.prev());
@ -237,10 +188,8 @@ public class MainActivity extends WebSocketActivityBase {
final LongPressTextView volumeDown = (LongPressTextView) findViewById(R.id.button_vol_down);
volumeDown.setOnTickListener((View view) -> playback.volumeDown());
notPlayingOrDisconnected.setOnClickListener((view) -> {
if (wss.getState() != WebSocketService.State.Connected) {
wss.reconnect();
}
disconnectedButton.setOnClickListener((view) -> {
wss.reconnect();
});
findViewById(R.id.button_artists).setOnClickListener((View view) -> {
@ -266,61 +215,6 @@ public class MainActivity extends WebSocketActivityBase {
disconnectedOverlay.setOnClickListener((view) -> {
/* swallow input so user can't click on things while disconnected */
});
this.album.setOnClickListener((view) -> navigateToCurrentAlbum());
this.artist.setOnClickListener((view) -> navigateToCurrentArtist());
}
private void rebindAlbumArtistWithArtTextView() {
final boolean buffering = playback.getPlaybackState() == PlaybackState.Buffering;
final String artist = playback.getTrackString(
Metadata.Track.ARTIST, getString(buffering ? R.string.buffering : R.string.unknown_artist));
final String album = playback.getTrackString(
Metadata.Track.ALBUM, getString(buffering ? R.string.buffering : R.string.unknown_album));
final ForegroundColorSpan albumColor =
new ForegroundColorSpan(getResources().getColor(R.color.theme_orange));
final ForegroundColorSpan artistColor =
new ForegroundColorSpan(getResources().getColor(R.color.theme_yellow));
final SpannableStringBuilder builder =
new SpannableStringBuilder().append(album).append(" - ").append(artist);
final ClickableSpan albumClickable = new ClickableSpan() {
@Override
public void onClick(View widget) {
navigateToCurrentAlbum();
}
@Override
public void updateDrawState(TextPaint ds) {
}
};
final ClickableSpan artistClickable = new ClickableSpan() {
@Override
public void onClick(View widget) {
navigateToCurrentArtist();
}
@Override
public void updateDrawState(TextPaint ds) {
}
};
int artistOffset = album.length() + 3;
builder.setSpan(albumColor, 0, album.length(), 0);
builder.setSpan(albumClickable, 0, album.length(), 0);
builder.setSpan(artistColor, artistOffset, artistOffset + artist.length(), 0);
builder.setSpan(artistClickable, artistOffset, artistOffset + artist.length(), 0);
this.artistAndAlbumWithArt.setMovementMethod(LinkMovementMethod.getInstance());
this.artistAndAlbumWithArt.setHighlightColor(Color.TRANSPARENT);
this.artistAndAlbumWithArt.setText(builder);
}
private void rebindUi() {
@ -328,238 +222,56 @@ public class MainActivity extends WebSocketActivityBase {
throw new IllegalStateException();
}
/* state management for UI stuff is starting to get out of hand. we should
refactor things pretty soon before they're completely out of control */
final boolean streaming = prefs.getBoolean(
Prefs.Key.STREAMING_PLAYBACK, Prefs.Default.STREAMING_PLAYBACK);
final WebSocketService.State state = wss.getState();
final boolean connected = state == WebSocketService.State.Connected;
final boolean streaming = prefs.getBoolean(Prefs.Key.STREAMING_PLAYBACK, Prefs.Default.STREAMING_PLAYBACK);
final boolean connected = (wss.getState() == WebSocketService.State.Connected);
final boolean stopped = (playback.getPlaybackState() == PlaybackState.Stopped);
final boolean playing = (playback.getPlaybackState() == PlaybackState.Playing);
final boolean showMetadataView = !stopped && connected && playback.getQueueCount() > 0;
/* bottom section: transport controls */
this.playPause.setText(playing ? R.string.button_pause : R.string.button_play);
final boolean stopped = (playback.getPlaybackState() == PlaybackState.Stopped);
notPlayingOrDisconnected.setVisibility(!connected || stopped ? View.VISIBLE : View.GONE);
boolean buffering = playback.getPlaybackState() == PlaybackState.Buffering;
final boolean stateIsValidForArtwork = !stopped && connected && playback.getQueueCount() > 0;
this.connected.setVisibility((connected && stopped) ? View.VISIBLE : View.GONE);
this.connectedNotPlaying.setVisibility((connected && stopped) ? View.VISIBLE : View.GONE);
this.disconnectedOverlay.setVisibility(connected ? View.GONE : View.VISIBLE);
/* setup our state as if we have no album art -- because we don't know if we have any
yet! the album art load process (if enabled) will ensure the correct metadata block
is displayed in the correct location */
if (!stateIsValidForArtwork) {
setMetadataDisplayMode(DisplayMode.Stopped);
notPlayingOrDisconnected.setText(connected ? R.string.transport_not_playing : R.string.status_disconnected);
notPlayingOrDisconnected.setVisibility(View.VISIBLE);
}
else {
notPlayingOrDisconnected.setVisibility(View.GONE);
}
final String artist = playback.getTrackString(Metadata.Track.ARTIST, "");
final String album = playback.getTrackString(Metadata.Track.ALBUM, "");
final String title = playback.getTrackString(Metadata.Track.TITLE, "");
final String volume = getString(R.string.status_volume, Math.round(playback.getVolume() * 100));
this.title.setText(Strings.empty(title) ? getString(buffering ? R.string.buffering : R.string.unknown_title) : title);
this.artist.setText(Strings.empty(artist) ? getString(buffering ? R.string.buffering : R.string.unknown_artist) : artist);
this.album.setText(Strings.empty(album) ? getString(buffering ? R.string.buffering : R.string.unknown_album) : album);
this.volume.setText(volume);
this.rebindAlbumArtistWithArtTextView();
this.titleWithArt.setText(Strings.empty(title) ? getString(buffering ? R.string.buffering : R.string.unknown_title) : title);
this.volumeWithArt.setText(volume);
this.buffering.setVisibility(buffering ? View.VISIBLE : View.GONE);
this.bufferingWithArt.setVisibility(buffering ? View.VISIBLE : View.GONE);
final RepeatMode repeatMode = playback.getRepeatMode();
final boolean repeatChecked = (repeatMode != RepeatMode.None);
repeatCb.setText(REPEAT_TO_STRING_ID.get(repeatMode));
Views.setCheckWithoutEvent(repeatCb, repeatChecked, this.repeatListener);
this.shuffleCb.setText(streaming ? R.string.button_random : R.string.button_shuffle);
Views.setCheckWithoutEvent(this.shuffleCb, playback.isShuffled(), this.shuffleListener);
Views.setCheckWithoutEvent(this.muteCb, playback.isMuted(), this.muteListener);
boolean albumArtEnabledInSettings = this.prefs.getBoolean(
Prefs.Key.ALBUM_ART_ENABLED, Prefs.Default.ALBUM_ART_ENABLED);
/* middle section: connected, disconnected, and metadata views */
connectedNotPlaying.setVisibility(View.GONE);
disconnectedButton.setVisibility(View.GONE);
if (stateIsValidForArtwork) {
if (!albumArtEnabledInSettings || Strings.empty(artist) || Strings.empty(album)) {
this.albumArtModel = AlbumArtModel.empty();
setMetadataDisplayMode(DisplayMode.NoArtwork);
}
else {
if (!this.albumArtModel.is(artist, album)) {
this.albumArtModel.destroy();
if (!showMetadataView) {
metadataView.hide();
this.albumArtModel = new AlbumArtModel(
title, artist, album, AlbumArtModel.Size.Mega, albumArtRetrieved);
}
updateAlbumArt();
if (!connected) {
disconnectedButton.setVisibility(View.VISIBLE);
}
else if (stopped) {
connectedNotPlaying.setVisibility(View.VISIBLE);
}
}
else {
metadataView.refresh();
}
}
private void clearUi() {
albumArtModel = AlbumArtModel.empty();
updateAlbumArt();
metadataView.clear();
rebindUi();
}
private void setMetadataDisplayMode(DisplayMode mode) {
lastDisplayMode = mode;
if (metadataAnim1 != null) {
metadataAnim1.cancel();
metadataAnim2.cancel();
}
if (mode == DisplayMode.Stopped) {
albumArtImageView.setImageDrawable(null);
metadataAnim1 = Views.animateAlpha(mainTrackMetadataWithAlbumArt, 0.0f);
metadataAnim2 = Views.animateAlpha(mainTrackMetadataNoAlbumArt, 0.0f);
}
else if (mode == DisplayMode.Artwork) {
metadataAnim1 = Views.animateAlpha(mainTrackMetadataWithAlbumArt, 1.0f);
metadataAnim2 = Views.animateAlpha(mainTrackMetadataNoAlbumArt, 0.0f);
}
else {
albumArtImageView.setImageDrawable(null);
metadataAnim1 = Views.animateAlpha(mainTrackMetadataWithAlbumArt, 0.0f);
metadataAnim2 = Views.animateAlpha(mainTrackMetadataNoAlbumArt, 1.0f);
}
}
private void preloadNextImage() {
final SocketMessage request = SocketMessage.Builder
.request(Messages.Request.QueryPlayQueueTracks)
.addOption(Messages.Key.OFFSET, this.playback.getQueuePosition() + 1)
.addOption(Messages.Key.LIMIT, 1)
.build();
this.wss.send(request, this.getWebSocketServiceClient(), (response) -> {
final JSONArray data = response.getJsonArrayOption(Messages.Key.DATA, new JSONArray());
if (data.length() > 0) {
JSONObject track = data.optJSONObject(0);
final String artist = track.optString(Metadata.Track.ARTIST, "");
final String album = track.optString(Metadata.Track.ALBUM, "");
if (!albumArtModel.is(artist, album)) {
new AlbumArtModel("", artist, album, AlbumArtModel.Size.Mega, (info, url) -> {
int width = albumArtImageView.getWidth();
int height = albumArtImageView.getHeight();
Glide.with(MainActivity.this).load(url).downloadOnly(width, height);
}).fetch();
}
}
});
}
private void updateAlbumArt() {
if (playback.getPlaybackState() == PlaybackState.Stopped) {
setMetadataDisplayMode(DisplayMode.NoArtwork);
}
final String url = albumArtModel.getUrl();
if (Strings.empty(url)) {
this.lastArtworkUrl = null;
albumArtModel.fetch();
setMetadataDisplayMode(DisplayMode.NoArtwork);
}
else if (!url.equals(lastArtworkUrl) || lastDisplayMode == DisplayMode.Stopped) {
final int loadId = albumArtModel.getId();
this.lastArtworkUrl = url;
Glide.with(this)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.listener(new RequestListener<String, GlideDrawable>() {
@Override
public boolean onException(Exception e,
String model,
Target<GlideDrawable> target,
boolean isFirstResource)
{
setMetadataDisplayMode(DisplayMode.NoArtwork);
lastArtworkUrl = null;
return false;
}
@Override
public boolean onResourceReady(GlideDrawable resource,
String model,
Target<GlideDrawable> target,
boolean isFromMemoryCache,
boolean isFirstResource)
{
if (!isPaused()) {
preloadNextImage();
}
/* if the loadId doesn't match the current id, then the image was
loaded for a different song. throw it away. */
if (albumArtModel.getId() != loadId) {
return true;
}
else {
setMetadataDisplayMode(DisplayMode.Artwork);
return false;
}
}
})
.into(albumArtImageView);
}
else {
setMetadataDisplayMode(lastDisplayMode);
}
}
private void navigateToCurrentArtist() {
final long artistId = playback.getTrackLong(Metadata.Track.ARTIST_ID, -1);
if (artistId != -1) {
final String artistName = playback.getTrackString(Metadata.Track.ARTIST, "");
startActivity(AlbumBrowseActivity.getStartIntent(
MainActivity.this, Messages.Category.ARTIST, artistId, artistName));
}
}
private void navigateToCurrentAlbum() {
final long albumId = playback.getTrackLong(Metadata.Track.ALBUM_ID, -1);
if (albumId != -1) {
final String albumName = playback.getTrackString(Metadata.Track.ALBUM, "");
startActivity(TrackListActivity.getStartIntent(
MainActivity.this, Messages.Category.ALBUM, albumId, albumName));
}
}
private void navigateToPlayQueue() {
startActivity(PlayQueueActivity.getStartIntent(MainActivity.this, playback.getQueuePosition()));
}
private AlbumArtModel.AlbumArtCallback albumArtRetrieved = (model, url) -> {
handler.post(() -> {
if (model == albumArtModel) {
if (Strings.empty(model.getUrl())) {
setMetadataDisplayMode(DisplayMode.NoArtwork);
}
else {
updateAlbumArt();
}
}
});
};
private CheckBox.OnCheckedChangeListener muteListener =
(CompoundButton compoundButton, boolean b) -> {
if (b != playback.isMuted()) {

View File

@ -379,7 +379,7 @@ public class SystemService extends Service {
return true;
case ACTION_NOTIFICATION_STOP:
this.playback.pause();
this.playback.stop();
SystemService.shutdown();
return true;
}

View File

@ -0,0 +1,379 @@
package io.casey.musikcube.remote.ui.view;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.os.Handler;
import android.support.annotation.AttrRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.SpannableStringBuilder;
import android.text.TextPaint;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.text.style.ForegroundColorSpan;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.resource.drawable.GlideDrawable;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.Target;
import org.json.JSONArray;
import org.json.JSONObject;
import io.casey.musikcube.remote.R;
import io.casey.musikcube.remote.playback.Metadata;
import io.casey.musikcube.remote.playback.PlaybackService;
import io.casey.musikcube.remote.playback.PlaybackState;
import io.casey.musikcube.remote.ui.activity.AlbumBrowseActivity;
import io.casey.musikcube.remote.ui.activity.TrackListActivity;
import io.casey.musikcube.remote.ui.model.AlbumArtModel;
import io.casey.musikcube.remote.ui.util.Views;
import io.casey.musikcube.remote.util.Strings;
import io.casey.musikcube.remote.websocket.Messages;
import io.casey.musikcube.remote.websocket.Prefs;
import io.casey.musikcube.remote.websocket.SocketMessage;
import io.casey.musikcube.remote.websocket.WebSocketService;
public class MainMetadataView extends FrameLayout {
private Handler handler = new Handler();
private PlaybackService playback;
private WebSocketService wss = null;
private SharedPreferences prefs;
private boolean isPaused = true;
private TextView title, artist, album, volume;
private TextView titleWithArt, artistAndAlbumWithArt, volumeWithArt;
private View mainTrackMetadataWithAlbumArt, mainTrackMetadataNoAlbumArt;
private View buffering, bufferingWithArt;
private ImageView albumArtImageView;
private ViewPropertyAnimator metadataAnim1, metadataAnim2;
private enum DisplayMode { Artwork, NoArtwork, Stopped }
private AlbumArtModel albumArtModel = AlbumArtModel.empty();
private DisplayMode lastDisplayMode = DisplayMode.Stopped;
private String lastArtworkUrl = null;
public MainMetadataView(@NonNull Context context) {
super(context);
init();
}
public MainMetadataView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public MainMetadataView(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public void onResume(final PlaybackService playback) {
this.wss.addClient(wssClient);
this.playback = playback;
isPaused = false;
}
public void onPause() {
this.wss.removeClient(wssClient);
isPaused = true;
}
public void clear() {
albumArtModel = AlbumArtModel.empty();
updateAlbumArt();
}
public void hide() {
setVisibility(View.GONE);
}
public void refresh() {
setVisibility(View.VISIBLE);
final boolean buffering = playback.getPlaybackState() == PlaybackState.Buffering;
final String artist = playback.getTrackString(Metadata.Track.ARTIST, "");
final String album = playback.getTrackString(Metadata.Track.ALBUM, "");
final String title = playback.getTrackString(Metadata.Track.TITLE, "");
final String volume = getString(R.string.status_volume, Math.round(playback.getVolume() * 100));
this.title.setText(Strings.empty(title) ? getString(buffering ? R.string.buffering : R.string.unknown_title) : title);
this.artist.setText(Strings.empty(artist) ? getString(buffering ? R.string.buffering : R.string.unknown_artist) : artist);
this.album.setText(Strings.empty(album) ? getString(buffering ? R.string.buffering : R.string.unknown_album) : album);
this.volume.setText(volume);
this.rebindAlbumArtistWithArtTextView();
this.titleWithArt.setText(Strings.empty(title) ? getString(buffering ? R.string.buffering : R.string.unknown_title) : title);
this.volumeWithArt.setText(volume);
this.buffering.setVisibility(buffering ? View.VISIBLE : View.GONE);
this.bufferingWithArt.setVisibility(buffering ? View.VISIBLE : View.GONE);
boolean albumArtEnabledInSettings = this.prefs.getBoolean(
Prefs.Key.ALBUM_ART_ENABLED, Prefs.Default.ALBUM_ART_ENABLED);
if (!albumArtEnabledInSettings || Strings.empty(artist) || Strings.empty(album)) {
this.albumArtModel = AlbumArtModel.empty();
setMetadataDisplayMode(DisplayMode.NoArtwork);
}
else {
if (!this.albumArtModel.is(artist, album)) {
this.albumArtModel.destroy();
this.albumArtModel = new AlbumArtModel(
title, artist, album, AlbumArtModel.Size.Mega, albumArtRetrieved);
}
updateAlbumArt();
}
}
private String getString(int resId) {
return getContext().getString(resId);
}
private String getString(int resId, Object... args) {
return getContext().getString(resId, args);
}
private void setMetadataDisplayMode(DisplayMode mode) {
lastDisplayMode = mode;
if (metadataAnim1 != null) {
metadataAnim1.cancel();
metadataAnim2.cancel();
}
if (mode == DisplayMode.Stopped) {
albumArtImageView.setImageDrawable(null);
metadataAnim1 = Views.animateAlpha(mainTrackMetadataWithAlbumArt, 0.0f);
metadataAnim2 = Views.animateAlpha(mainTrackMetadataNoAlbumArt, 0.0f);
}
else if (mode == DisplayMode.Artwork) {
metadataAnim1 = Views.animateAlpha(mainTrackMetadataWithAlbumArt, 1.0f);
metadataAnim2 = Views.animateAlpha(mainTrackMetadataNoAlbumArt, 0.0f);
}
else {
albumArtImageView.setImageDrawable(null);
metadataAnim1 = Views.animateAlpha(mainTrackMetadataWithAlbumArt, 0.0f);
metadataAnim2 = Views.animateAlpha(mainTrackMetadataNoAlbumArt, 1.0f);
}
}
private void rebindAlbumArtistWithArtTextView() {
final boolean buffering = playback.getPlaybackState() == PlaybackState.Buffering;
final String artist = playback.getTrackString(
Metadata.Track.ARTIST, getString(buffering ? R.string.buffering : R.string.unknown_artist));
final String album = playback.getTrackString(
Metadata.Track.ALBUM, getString(buffering ? R.string.buffering : R.string.unknown_album));
final ForegroundColorSpan albumColor =
new ForegroundColorSpan(getResources().getColor(R.color.theme_orange));
final ForegroundColorSpan artistColor =
new ForegroundColorSpan(getResources().getColor(R.color.theme_yellow));
final SpannableStringBuilder builder =
new SpannableStringBuilder().append(album).append(" - ").append(artist);
final ClickableSpan albumClickable = new ClickableSpan() {
@Override
public void onClick(View widget) {
navigateToCurrentAlbum();
}
@Override
public void updateDrawState(TextPaint ds) {
}
};
final ClickableSpan artistClickable = new ClickableSpan() {
@Override
public void onClick(View widget) {
navigateToCurrentArtist();
}
@Override
public void updateDrawState(TextPaint ds) {
}
};
int artistOffset = album.length() + 3;
builder.setSpan(albumColor, 0, album.length(), 0);
builder.setSpan(albumClickable, 0, album.length(), 0);
builder.setSpan(artistColor, artistOffset, artistOffset + artist.length(), 0);
builder.setSpan(artistClickable, artistOffset, artistOffset + artist.length(), 0);
this.artistAndAlbumWithArt.setMovementMethod(LinkMovementMethod.getInstance());
this.artistAndAlbumWithArt.setHighlightColor(Color.TRANSPARENT);
this.artistAndAlbumWithArt.setText(builder);
}
private void updateAlbumArt() {
if (playback.getPlaybackState() == PlaybackState.Stopped) {
setMetadataDisplayMode(DisplayMode.NoArtwork);
}
final String url = albumArtModel.getUrl();
if (Strings.empty(url)) {
this.lastArtworkUrl = null;
albumArtModel.fetch();
setMetadataDisplayMode(DisplayMode.NoArtwork);
}
else if (!url.equals(lastArtworkUrl) || lastDisplayMode == DisplayMode.Stopped) {
final int loadId = albumArtModel.getId();
this.lastArtworkUrl = url;
Glide.with(getContext())
.load(url)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.listener(new RequestListener<String, GlideDrawable>() {
@Override
public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean first) {
setMetadataDisplayMode(DisplayMode.NoArtwork);
lastArtworkUrl = null;
return false;
}
@Override
public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean memory, boolean first) {
if (!isPaused) {
preloadNextImage();
}
/* if the loadId doesn't match the current id, then the image was
loaded for a different song. throw it away. */
if (albumArtModel.getId() != loadId) {
return true;
}
else {
setMetadataDisplayMode(DisplayMode.Artwork);
return false;
}
}
})
.into(albumArtImageView);
}
else {
setMetadataDisplayMode(lastDisplayMode);
}
}
private void preloadNextImage() {
final SocketMessage request = SocketMessage.Builder
.request(Messages.Request.QueryPlayQueueTracks)
.addOption(Messages.Key.OFFSET, this.playback.getQueuePosition() + 1)
.addOption(Messages.Key.LIMIT, 1)
.build();
this.wss.send(request, wssClient, (response) -> {
final JSONArray data = response.getJsonArrayOption(Messages.Key.DATA, new JSONArray());
if (data.length() > 0) {
JSONObject track = data.optJSONObject(0);
final String artist = track.optString(Metadata.Track.ARTIST, "");
final String album = track.optString(Metadata.Track.ALBUM, "");
if (!albumArtModel.is(artist, album)) {
new AlbumArtModel("", artist, album, AlbumArtModel.Size.Mega, (info, url) -> {
int width = albumArtImageView.getWidth();
int height = albumArtImageView.getHeight();
Glide.with(getContext()).load(url).downloadOnly(width, height);
}).fetch();
}
}
});
}
private void init() {
this.prefs = getContext().getSharedPreferences(Prefs.NAME, Context.MODE_PRIVATE);
this.wss = WebSocketService.getInstance(getContext());
final View child = LayoutInflater.from(getContext())
.inflate(R.layout.main_metadata, this, false);
addView(child, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
this.title = (TextView) findViewById(R.id.track_title);
this.artist = (TextView) findViewById(R.id.track_artist);
this.album = (TextView) findViewById(R.id.track_album);
this.volume = (TextView) findViewById(R.id.volume);
this.buffering = findViewById(R.id.buffering);
this.titleWithArt = (TextView) findViewById(R.id.with_art_track_title);
this.artistAndAlbumWithArt = (TextView) findViewById(R.id.with_art_artist_and_album);
this.volumeWithArt = (TextView) findViewById(R.id.with_art_volume);
this.bufferingWithArt = findViewById(R.id.with_art_buffering);
this.mainTrackMetadataWithAlbumArt = findViewById(R.id.main_track_metadata_with_art);
this.mainTrackMetadataNoAlbumArt = findViewById(R.id.main_track_metadata_without_art);
this.albumArtImageView = (ImageView) findViewById(R.id.album_art);
/* these will get faded in as appropriate */
this.mainTrackMetadataNoAlbumArt.setAlpha(0.0f);
this.mainTrackMetadataWithAlbumArt.setAlpha(0.0f);
this.album.setOnClickListener((view) -> navigateToCurrentAlbum());
this.artist.setOnClickListener((view) -> navigateToCurrentArtist());
}
private void navigateToCurrentArtist() {
final long artistId = playback.getTrackLong(Metadata.Track.ARTIST_ID, -1);
if (artistId != -1) {
final String artistName = playback.getTrackString(Metadata.Track.ARTIST, "");
getContext().startActivity(AlbumBrowseActivity.getStartIntent(
getContext(), Messages.Category.ARTIST, artistId, artistName));
}
}
private void navigateToCurrentAlbum() {
final long albumId = playback.getTrackLong(Metadata.Track.ALBUM_ID, -1);
if (albumId != -1) {
final String albumName = playback.getTrackString(Metadata.Track.ALBUM, "");
getContext().startActivity(TrackListActivity.getStartIntent(
getContext(), Messages.Category.ALBUM, albumId, albumName));
}
}
private WebSocketService.Client wssClient = new WebSocketService.Client() {
@Override
public void onStateChanged(WebSocketService.State newState, WebSocketService.State oldState) {
}
@Override
public void onMessageReceived(SocketMessage message) {
}
@Override
public void onInvalidPassword() {
}
};
private AlbumArtModel.AlbumArtCallback albumArtRetrieved = (model, url) -> {
handler.post(() -> {
if (model == albumArtModel) {
if (Strings.empty(model.getUrl())) {
setMetadataDisplayMode(DisplayMode.NoArtwork);
}
else {
updateAlbumArt();
}
}
});
};
}

View File

@ -9,157 +9,51 @@
tools:context="io.casey.musikcube.remote.MainActivity">
<FrameLayout
android:id="@+id/metadata_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/play_controls">
<ImageView
android:id="@+id/album_art"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"/>
<LinearLayout
android:id="@+id/connected_not_playing"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center">
<TextView
android:id="@+id/connected"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="@color/theme_green"
android:text="connected"/>
android:text="@string/button_connected"/>
<TextView
android:id="@+id/main_not_playing"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textSize="@dimen/text_size_large"
android:textColor="@color/theme_disabled_foreground"
android:padding="8dp"
android:clickable="true"
android:background="@drawable/not_playing_button"
android:text="@string/transport_not_playing"/>
</LinearLayout>
<LinearLayout
android:id="@+id/main_track_metadata_with_art"
android:background="@color/main_playback_metadata"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:orientation="vertical"
android:paddingLeft="24dp"
android:paddingRight="24dp"
android:paddingTop="2dp"
android:paddingBottom="2dp">
<ProgressBar
android:id="@+id/with_art_buffering"
style="?android:attr/progressBarStyleSmall"
android:layout_marginTop="2dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
<TextView
style="@style/LightDropShadow"
android:id="@+id/with_art_track_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="center"
android:textColor="@color/theme_green"
android:text="-"/>
<TextView
style="@style/LightDropShadow"
android:id="@+id/with_art_artist_and_album"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="center"
android:textColor="@color/theme_yellow"
android:text="-"/>
<TextView
style="@style/LightDropShadow"
android:id="@+id/with_art_volume"
android:textSize="@dimen/text_size_small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="center"
android:layout_marginBottom="2dp"
android:text=""/>
</LinearLayout>
<LinearLayout
android:id="@+id/main_track_metadata_without_art"
android:layout_width="match_parent"
<TextView
android:id="@+id/disconnected"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical"
android:paddingLeft="24dp"
android:paddingRight="24dp"
android:paddingTop="6dp"
android:paddingBottom="6dp">
android:textSize="@dimen/text_size_large"
android:textColor="@color/theme_disabled_foreground"
android:padding="8dp"
android:clickable="true"
android:background="@drawable/not_playing_button"
android:text="@string/status_disconnected"/>
<ProgressBar
android:id="@+id/buffering"
style="?android:attr/progressBarStyleSmall"
android:layout_marginTop="2dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
<TextView
android:id="@+id/track_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="center"
android:textColor="@color/theme_green"
android:textSize="@dimen/text_size_xxlarge"
android:text="-"/>
<TextView
android:id="@+id/track_album"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="center"
android:textColor="@color/theme_orange"
android:textSize="@dimen/text_size_xlarge"
android:text="-"/>
<TextView
android:id="@+id/track_artist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="center"
android:layout_marginTop="2dp"
android:textColor="@color/theme_yellow"
android:textSize="@dimen/text_size_large"
android:text="-"/>
<TextView
android:id="@+id/volume"
android:textSize="@dimen/text_size_small"
android:layout_marginTop="4dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="center"
android:text=""/>
</LinearLayout>
<io.casey.musikcube.remote.ui.view.MainMetadataView
android:id="@+id/main_metadata_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>

View File

@ -0,0 +1,127 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/metadata_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/album_art"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"/>
<LinearLayout
android:id="@+id/main_track_metadata_with_art"
android:background="@color/main_playback_metadata"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:orientation="vertical"
android:paddingLeft="24dp"
android:paddingRight="24dp"
android:paddingTop="2dp"
android:paddingBottom="2dp">
<ProgressBar
android:id="@+id/with_art_buffering"
style="?android:attr/progressBarStyleSmall"
android:layout_marginTop="2dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
<TextView
style="@style/LightDropShadow"
android:id="@+id/with_art_track_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="center"
android:textColor="@color/theme_green"
android:text="-"/>
<TextView
style="@style/LightDropShadow"
android:id="@+id/with_art_artist_and_album"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="center"
android:textColor="@color/theme_yellow"
android:text="-"/>
<TextView
style="@style/LightDropShadow"
android:id="@+id/with_art_volume"
android:textSize="@dimen/text_size_small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="center"
android:layout_marginBottom="2dp"
android:text=""/>
</LinearLayout>
<LinearLayout
android:id="@+id/main_track_metadata_without_art"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical"
android:paddingLeft="24dp"
android:paddingRight="24dp"
android:paddingTop="6dp"
android:paddingBottom="6dp">
<ProgressBar
android:id="@+id/buffering"
style="?android:attr/progressBarStyleSmall"
android:layout_marginTop="2dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
<TextView
android:id="@+id/track_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="center"
android:textColor="@color/theme_green"
android:textSize="@dimen/text_size_xxlarge"
android:text="-"/>
<TextView
android:id="@+id/track_album"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="center"
android:textColor="@color/theme_orange"
android:textSize="@dimen/text_size_xlarge"
android:text="-"/>
<TextView
android:id="@+id/track_artist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="center"
android:layout_marginTop="2dp"
android:textColor="@color/theme_yellow"
android:textSize="@dimen/text_size_large"
android:text="-"/>
<TextView
android:id="@+id/volume"
android:textSize="@dimen/text_size_small"
android:layout_marginTop="4dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="center"
android:text=""/>
</LinearLayout>
</FrameLayout>

View File

@ -41,6 +41,7 @@
<string name="status_disconnected">disconnected</string>
<string name="status_connected">connected to %1$s</string>
<string name="status_volume">volume %1d%%</string>
<string name="button_connected">connected</string>
<string name="edit_connection_info">connection info:</string>
<string name="edit_connection_hostname">ip address or hostname</string>
<string name="edit_connection_port">main server port (default 7905)</string>