- Added support for genres and playlists

- Fixed some deep linking issues from transport controls in the main
  window
- Various other small cleanups and polish
This commit is contained in:
casey langen 2017-02-17 21:54:52 -08:00
parent 2e765da9fc
commit 375e0d0e28
9 changed files with 162 additions and 71 deletions

View File

@ -31,6 +31,30 @@ public class AlbumBrowseActivity extends WebSocketActivityBase implements Filter
.putExtra(EXTRA_CATEGORY_ID, categoryId);
}
public static Intent getStartIntent(final Context context,
final String categoryName,
long categoryId,
final String categoryValue)
{
final Intent intent = getStartIntent(context, categoryName, categoryId);
if (Strings.notEmpty(categoryValue)) {
intent.putExtra(
AlbumBrowseActivity.EXTRA_TITLE,
context.getString(R.string.albums_by_title, categoryValue));
}
return intent;
}
public static Intent getStartIntent(final Context context,
final String categoryName,
final JSONObject categoryJson)
{
final String value = categoryJson.optString(Messages.Key.VALUE);
final long categoryId = categoryJson.optLong(Messages.Key.ID);
return getStartIntent(context, categoryName, categoryId, value);
}
private WebSocketService wss;
private Adapter adapter;
@ -123,18 +147,12 @@ public class AlbumBrowseActivity extends WebSocketActivityBase implements Filter
};
private View.OnClickListener onItemClickListener = (View view) -> {
JSONObject album = (JSONObject) view.getTag();
long id = album.optLong(Key.ID);
String title = album.optString(Key.TITLE, "");
final JSONObject album = (JSONObject) view.getTag();
final long id = album.optLong(Key.ID);
final String title = album.optString(Key.TITLE, "");
final Intent intent = TrackListActivity.getStartIntent(
AlbumBrowseActivity.this, Messages.Category.ALBUM, id);
if (Strings.notEmpty(title)) {
intent.putExtra(
TrackListActivity.EXTRA_TITLE,
getString(R.string.songs_from_category, title));
}
AlbumBrowseActivity.this, Messages.Category.ALBUM, id, title);
startActivityForResult(intent, Navigation.RequestCode.ALBUM_TRACKS_ACTIVITY);
};

View File

@ -19,20 +19,28 @@ import java.util.Map;
public class CategoryBrowseActivity extends WebSocketActivityBase implements Filterable {
private static final String EXTRA_CATEGORY = "extra_category";
private static final String EXTRA_DEEP_LINK_TYPE = "extra_deep_link_type";
public interface DeepLink {
int TRACKS = 0;
int ALBUMS = 1;
}
private static final Map<String, String> CATEGORY_NAME_TO_ID = new HashMap<>();
private static final Map<String, Integer> CATEGORY_NAME_TO_TITLE = new HashMap<>();
static {
CATEGORY_NAME_TO_ID.put(Messages.Key.ALBUM_ARTIST, Messages.Key.ALBUM_ARTIST_ID);
CATEGORY_NAME_TO_ID.put(Messages.Key.GENRE, Messages.Key.GENRE_ID);
CATEGORY_NAME_TO_ID.put(Messages.Key.ARTIST, Messages.Key.ARTIST_ID);
CATEGORY_NAME_TO_ID.put(Messages.Key.ALBUM, Messages.Key.ALBUM_ID);
CATEGORY_NAME_TO_ID.put(Messages.Category.ALBUM_ARTIST, Messages.Key.ALBUM_ARTIST_ID);
CATEGORY_NAME_TO_ID.put(Messages.Category.GENRE, Messages.Key.GENRE_ID);
CATEGORY_NAME_TO_ID.put(Messages.Category.ARTIST, Messages.Key.ARTIST_ID);
CATEGORY_NAME_TO_ID.put(Messages.Category.ALBUM, Messages.Key.ALBUM_ID);
CATEGORY_NAME_TO_ID.put(Messages.Category.PLAYLISTS, Messages.Key.ALBUM_ID);
CATEGORY_NAME_TO_TITLE.put(Messages.Key.ALBUM_ARTIST, R.string.artists_title);
CATEGORY_NAME_TO_TITLE.put(Messages.Key.GENRE, R.string.genres_title);
CATEGORY_NAME_TO_TITLE.put(Messages.Key.ARTIST, R.string.artists_title);
CATEGORY_NAME_TO_TITLE.put(Messages.Key.ALBUM, R.string.albums_title);
CATEGORY_NAME_TO_TITLE.put(Messages.Category.ALBUM_ARTIST, R.string.artists_title);
CATEGORY_NAME_TO_TITLE.put(Messages.Category.GENRE, R.string.genres_title);
CATEGORY_NAME_TO_TITLE.put(Messages.Category.ARTIST, R.string.artists_title);
CATEGORY_NAME_TO_TITLE.put(Messages.Category.ALBUM, R.string.albums_title);
CATEGORY_NAME_TO_TITLE.put(Messages.Category.PLAYLISTS, R.string.playlists_title);
}
public static Intent getStartIntent(final Context context, final String category) {
@ -40,16 +48,24 @@ public class CategoryBrowseActivity extends WebSocketActivityBase implements Fil
.putExtra(EXTRA_CATEGORY, category);
}
public static Intent getStartIntent(final Context context, final String category, int deepLinkType) {
return new Intent(context, CategoryBrowseActivity.class)
.putExtra(EXTRA_CATEGORY, category)
.putExtra(EXTRA_DEEP_LINK_TYPE, deepLinkType);
}
private String category;
private Adapter adapter;
private String filter;
private TransportFragment transport;
private int deepLinkType;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.category = getIntent().getStringExtra(EXTRA_CATEGORY);
this.deepLinkType = getIntent().getIntExtra(EXTRA_DEEP_LINK_TYPE, DeepLink.ALBUMS);
this.adapter = new Adapter();
setContentView(R.layout.recycler_view_activity);
@ -68,7 +84,9 @@ public class CategoryBrowseActivity extends WebSocketActivityBase implements Fil
@Override
public boolean onCreateOptionsMenu(Menu menu) {
Views.initSearchMenu(this, menu, this);
if (!Messages.Category.PLAYLISTS.equals(category)) { /* bleh */
Views.initSearchMenu(this, menu, this);
}
return true;
}
@ -127,37 +145,37 @@ public class CategoryBrowseActivity extends WebSocketActivityBase implements Fil
private View.OnClickListener onItemClickListener = (View view) -> {
final JSONObject entry = (JSONObject) view.getTag();
final long categoryId = entry.optLong(Messages.Key.ID);
final String value = entry.optString(Messages.Key.VALUE);
final Intent intent = AlbumBrowseActivity.getStartIntent(this, category, categoryId);
if (Strings.notEmpty(value)) {
intent.putExtra(
AlbumBrowseActivity.EXTRA_TITLE,
getString(R.string.albums_by_title, value));
if (deepLinkType == DeepLink.ALBUMS) {
navigateToAlbums(entry);
}
else {
navigateToTracks(entry);
}
startActivityForResult(intent, Navigation.RequestCode.ALBUM_BROWSE_ACTIVITY);
};
private View.OnLongClickListener onItemLongClickListener = (View view) -> {
final JSONObject entry = (JSONObject) view.getTag();
/* if we deep link to albums by default, long press will get to
tracks. if we deep link to tracks, just ignore */
if (deepLinkType == DeepLink.ALBUMS) {
navigateToTracks((JSONObject) view.getTag());
return true;
}
else {
return false;
}
};
private void navigateToAlbums(final JSONObject entry) {
final Intent intent = AlbumBrowseActivity.getStartIntent(this, category, entry);
startActivityForResult(intent, Navigation.RequestCode.ALBUM_BROWSE_ACTIVITY);
}
private void navigateToTracks(final JSONObject entry) {
final long categoryId = entry.optLong(Messages.Key.ID);
final String value = entry.optString(Messages.Key.VALUE);
final Intent intent = TrackListActivity.getStartIntent(this, category, categoryId);
if (Strings.notEmpty(value)) {
intent.putExtra(
TrackListActivity.EXTRA_TITLE,
getString(R.string.songs_from_category, value));
}
final Intent intent = TrackListActivity.getStartIntent(this, category, categoryId, value);
startActivityForResult(intent, Navigation.RequestCode.CATEGORY_TRACKS_ACTIVITY);
return true;
};
}
private class ViewHolder extends RecyclerView.ViewHolder {
private final TextView title;
@ -182,7 +200,11 @@ public class CategoryBrowseActivity extends WebSocketActivityBase implements Fil
titleColor = R.color.theme_green;
}
title.setText(entry.optString(Messages.Key.VALUE, "-"));
/* note optString only does a null check! */
String value = entry.optString(Messages.Key.VALUE, "");
value = Strings.empty(value) ? getString(R.string.unknown_value) : value;
title.setText(value);
title.setTextColor(getResources().getColor(titleColor));
itemView.setTag(entry);

View File

@ -33,7 +33,6 @@ import java.util.Map;
public class MainActivity extends WebSocketActivityBase {
private static Map<TransportModel.RepeatMode, Integer> REPEAT_TO_STRING_ID;
private static final int ARTIFICIAL_ARTWORK_DELAY_MILLIS = 0;
private WebSocketService wss = null;
private TransportModel model = new TransportModel();
@ -44,15 +43,15 @@ public class MainActivity extends WebSocketActivityBase {
private TextView notPlayingOrDisconnected;
private View connected;
private CheckBox shuffleCb, muteCb, repeatCb;
private ImageView albumArtImageView;
private View mainTrackMetadataWithAlbumArt, mainTrackMetadataNoAlbumArt;
private View disconnectedOverlay;
private Handler handler = new Handler();
private ViewPropertyAnimator metadataAnim1, metadataAnim2;
/* ugh, artwork related */
private enum DisplayMode { Artwork, NoArtwork, Stopped }
private View mainTrackMetadataWithAlbumArt, mainTrackMetadataNoAlbumArt;
private ViewPropertyAnimator metadataAnim1, metadataAnim2;
private AlbumArtModel albumArtModel = new AlbumArtModel();
private ImageView albumArtImageView;
static {
REPEAT_TO_STRING_ID = new HashMap<>();
@ -107,10 +106,21 @@ public class MainActivity extends WebSocketActivityBase {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_settings) {
startActivity(SettingsActivity.getStartIntent(this));
return true;
switch (item.getItemId()) {
case R.id.action_settings:
startActivity(SettingsActivity.getStartIntent(this));
return true;
case R.id.action_genres:
startActivity(CategoryBrowseActivity.getStartIntent(this, Messages.Category.GENRE));
return true;
case R.id.action_playlists:
startActivity(CategoryBrowseActivity.getStartIntent(
this, Messages.Category.PLAYLISTS, CategoryBrowseActivity.DeepLink.TRACKS));
return true;
}
return super.onOptionsItemSelected(item);
}
@ -306,7 +316,7 @@ public class MainActivity extends WebSocketActivityBase {
final boolean stopped = (model.getPlaybackState() == TransportModel.PlaybackState.Stopped);
notPlayingOrDisconnected.setVisibility(stopped ? View.VISIBLE : View.GONE);
final boolean stateIsValidForArtwork = !stopped && connected;
final boolean stateIsValidForArtwork = !stopped && connected && model.isValid();
this.connected.setVisibility((connected && stopped) ? View.VISIBLE : View.GONE);
this.disconnectedOverlay.setVisibility(connected ? View.GONE : View.VISIBLE);
@ -369,17 +379,19 @@ public class MainActivity extends WebSocketActivityBase {
metadataAnim2.cancel();
}
if (mode == DisplayMode.Artwork) {
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);
/* oh god why. hack to make volume % disappear. */
float noArtAlpha = (mode == DisplayMode.Stopped) ? 0.0f : 1.0f;
metadataAnim2 = Views.animateAlpha(mainTrackMetadataNoAlbumArt, noArtAlpha);
metadataAnim1 = Views.animateAlpha(mainTrackMetadataWithAlbumArt, 0.0f);
metadataAnim2 = Views.animateAlpha(mainTrackMetadataNoAlbumArt, 1.0f);
}
}
@ -462,16 +474,18 @@ public class MainActivity extends WebSocketActivityBase {
private void navigateToCurrentArtist() {
final long artistId = model.getTrackValueLong(TransportModel.Key.ARTIST_ID, -1);
if (artistId != -1) {
final String artistName = model.getTrackValueString(TransportModel.Key.ARTIST, "");
startActivity(AlbumBrowseActivity.getStartIntent(
MainActivity.this, Messages.Category.ARTIST, artistId));
MainActivity.this, Messages.Category.ARTIST, artistId, artistName));
}
}
private void navigateToCurrentAlbum() {
final long albumId = model.getTrackValueLong(TransportModel.Key.ALBUM_ID, -1);
if (albumId != -1) {
final String albumName = model.getTrackValueString(TransportModel.Key.ALBUM, "");
startActivity(TrackListActivity.getStartIntent(
MainActivity.this, Messages.Category.ALBUM, albumId));
MainActivity.this, Messages.Category.ALBUM, albumId, albumName));
}
}
@ -480,13 +494,11 @@ public class MainActivity extends WebSocketActivityBase {
}
private AlbumArtModel.AlbumArtCallback albumArtRetrieved = (model, url) -> {
long delay = Math.max(0, ARTIFICIAL_ARTWORK_DELAY_MILLIS - model.getLoadTimeMillis());
handler.postDelayed(() -> {
handler.post(() -> {
if (model == albumArtModel) {
updateAlbumArt();
}
}, delay);
});
};
private CheckBox.OnCheckedChangeListener muteListener =

View File

@ -108,5 +108,6 @@ public class Messages {
String ARTIST = "artist";
String ALBUM_ARTIST = "album_artist";
String GENRE = "genre";
String PLAYLISTS = "playlists";
}
}

View File

@ -34,7 +34,7 @@ public class SettingsActivity extends AppCompatActivity {
private void rebindUi() {
Views.setTextAndMoveCursorToEnd(this.addressText, prefs.getString("address", "192.168.1.100"));
Views.setTextAndMoveCursorToEnd(this.portText, String.format(Locale.ENGLISH, "%d", prefs.getInt("port", 9002)));
this.passwordText.setText(prefs.getString("password", ""));
Views.setTextAndMoveCursorToEnd(this.passwordText, prefs.getString("password", ""));
}
private void bindEventListeners() {

View File

@ -26,6 +26,21 @@ public class TrackListActivity extends WebSocketActivityBase implements Filterab
.putExtra(EXTRA_SELECTED_ID, id);
}
public static Intent getStartIntent(final Context context,
final String type,
final long id,
final String categoryValue) {
final Intent intent = getStartIntent(context, type, id);
if (Strings.notEmpty(categoryValue)) {
intent.putExtra(
TrackListActivity.EXTRA_TITLE,
context.getString(R.string.songs_from_category, categoryValue));
}
return intent;
}
public static Intent getStartIntent(final Context context) {
return new Intent(context, TrackListActivity.class);
}
@ -67,7 +82,9 @@ public class TrackListActivity extends WebSocketActivityBase implements Filterab
@Override
public boolean onCreateOptionsMenu(Menu menu) {
Views.initSearchMenu(this, menu, this);
if (!Messages.Category.PLAYLISTS.equals(categoryType)) {
Views.initSearchMenu(this, menu, this);
}
return true;
}

View File

@ -94,6 +94,7 @@ public class TransportModel {
private double duration;
private double currentTime;
private JSONObject track = new JSONObject();
private boolean valid = false;
public TransportModel() {
reset();
@ -135,6 +136,8 @@ public class TransportModel {
duration = message.getDoubleOption(Key.PLAYING_DURATION);
currentTime = message.getDoubleOption(Key.PLAYING_CURRENT_TIME);
track = message.getJsonObjectOption(Key.PLAYING_TRACK, new JSONObject());
valid = true;
return true;
}
@ -146,6 +149,11 @@ public class TransportModel {
queueCount = queuePosition = 0;
duration = currentTime = 0.0f;
track = new JSONObject();
valid = false;
}
public boolean isValid() {
return valid;
}
public PlaybackState getPlaybackState() {
@ -156,10 +164,6 @@ public class TransportModel {
return repeatMode;
}
public void setRepeatMode(final RepeatMode repeatMode) {
this.repeatMode = repeatMode;
}
public boolean isShuffled() {
return shuffled;
}

View File

@ -1,8 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_playlists"
app:showAsAction="never"
android:title="@string/menu_playlists"/>
<item
android:id="@+id/action_genres"
app:showAsAction="never"
android:title="@string/menu_genres"/>
<item
android:id="@+id/action_settings"
app:showAsAction="never"
android:title="@string/menu_settings"/>
</menu>

View File

@ -8,6 +8,7 @@
<string name="songs_from_category">songs from \'%1$s\'</string>
<string name="genres_title">genres</string>
<string name="artists_title">artists</string>
<string name="playlists_title">playlists</string>
<string name="button_pause">pause</string>
<string name="button_play">play</string>
<string name="button_prev">prev</string>
@ -42,4 +43,7 @@
<string name="transport_not_playing">not playing</string>
<string name="search_hint">search</string>
<string name="menu_settings">settings</string>
<string name="menu_genres">genres</string>
<string name="menu_playlists">playlists</string>
<string name="unknown_value">&lt;unknown&gt;</string>
</resources>