/*
* Copyright (C) 2014 Fastboot Mobile, LLC.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
* the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, see <http://www.gnu.org/licenses>.
*/
package com.fastbootmobile.encore.app.fragments;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.PagerTabStrip;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.support.v7.graphics.Palette;
import android.support.v7.widget.CardView;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.echonest.api.v4.Biography;
import com.echonest.api.v4.EchoNestException;
import com.fastbootmobile.encore.api.echonest.EchoNest;
import com.fastbootmobile.encore.app.AppActivity;
import com.fastbootmobile.encore.app.ArtistActivity;
import com.fastbootmobile.encore.app.R;
import com.fastbootmobile.encore.app.adapters.ArtistsAdapter;
import com.fastbootmobile.encore.app.ui.AlbumArtImageView;
import com.fastbootmobile.encore.app.ui.MaterialTransitionDrawable;
import com.fastbootmobile.encore.app.ui.ObservableScrollView;
import com.fastbootmobile.encore.app.ui.ParallaxScrollView;
import com.fastbootmobile.encore.app.ui.PlayPauseDrawable;
import com.fastbootmobile.encore.app.ui.WrapContentHeightViewPager;
import com.fastbootmobile.encore.art.AlbumArtHelper;
import com.fastbootmobile.encore.art.RecyclingBitmapDrawable;
import com.fastbootmobile.encore.framework.PlaybackProxy;
import com.fastbootmobile.encore.framework.PluginsLookup;
import com.fastbootmobile.encore.framework.Suggestor;
import com.fastbootmobile.encore.model.Album;
import com.fastbootmobile.encore.model.Artist;
import com.fastbootmobile.encore.model.BoundEntity;
import com.fastbootmobile.encore.model.Playlist;
import com.fastbootmobile.encore.model.SearchResult;
import com.fastbootmobile.encore.model.Song;
import com.fastbootmobile.encore.providers.ILocalCallback;
import com.fastbootmobile.encore.providers.IMusicProvider;
import com.fastbootmobile.encore.providers.ProviderAggregator;
import com.fastbootmobile.encore.providers.ProviderConnection;
import com.fastbootmobile.encore.providers.ProviderIdentifier;
import com.fastbootmobile.encore.service.BasePlaybackCallback;
import com.fastbootmobile.encore.service.PlaybackService;
import com.fastbootmobile.encore.utils.Utils;
import com.getbase.floatingactionbutton.FloatingActionButton;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
/**
* Fragment showing artist information: Tracks, similar artists, and biography
*/
public class ArtistFragment extends Fragment implements ILocalCallback {
private static final String TAG = "ArtistFragment";
private static final int FRAGMENT_ID_TRACKS = 0;
private static final int FRAGMENT_ID_SIMILAR = 1;
private static final int FRAGMENT_ID_BIOGRAPHY = 2;
private static final int FRAGMENT_COUNT = 3;
private static final int ANIMATION_DURATION = 300;
private static final DecelerateInterpolator mInterpolator = new DecelerateInterpolator();
private Bitmap mHeroImage;
private int mBackgroundColor;
private Artist mArtist;
private ParallaxScrollView mRootView;
private Handler mHandler;
private PlayPauseDrawable mFabDrawable;
private boolean mFabShouldResume;
private ArtistTracksFragment mArtistTracksFragment;
private ArtistInfoFragment mArtistInfoFragment;
private ArtistSimilarFragment mArtistSimilarFragment;
private FloatingActionButton mFabPlay;
private RecyclingBitmapDrawable mLogoBitmap;
private ImageView mHeroImageView;
private Runnable mUpdateAlbumsRunnable = new Runnable() {
@Override
public void run() {
ProviderAggregator aggregator = ProviderAggregator.getDefault();
mArtist = aggregator.retrieveArtist(mArtist.getRef(), mArtist.getProvider());
mArtistTracksFragment.loadRecommendation();
mArtistTracksFragment.loadAlbums(false);
}
};
private BasePlaybackCallback mPlaybackCallback = new BasePlaybackCallback() {
@Override
public void onSongStarted(final boolean buffering, Song s) throws RemoteException {
mHandler.post(new Runnable() {
@Override
public void run() {
mFabDrawable.setShape(PlayPauseDrawable.SHAPE_PAUSE);
mFabDrawable.setBuffering(buffering);
}
});
}
@Override
public void onPlaybackPause() throws RemoteException {
mHandler.post(new Runnable() {
@Override
public void run() {
mFabDrawable.setShape(PlayPauseDrawable.SHAPE_PLAY);
mFabDrawable.setBuffering(false);
}
});
}
@Override
public void onPlaybackResume() throws RemoteException {
mHandler.post(new Runnable() {
@Override
public void run() {
mFabDrawable.setShape(PlayPauseDrawable.SHAPE_PAUSE);
mFabDrawable.setBuffering(false);
}
});
}
};
/**
* Class handling the ViewPager for the tabs
*/
private class ViewPagerAdapter extends FragmentPagerAdapter {
private boolean mHasRosetta;
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
// Depending on whether we have a rosetta-enabled provider or not, we will
// show or not the Similar tab.
mHasRosetta = ProviderAggregator.getDefault().getRosettaStonePrefix().size() > 0;
}
/**
* {@inheritDoc}
*/
@Override
public Fragment getItem(int i) {
// We're deliberately not using constants in this switch because values may be two
// different things.
switch (i) {
case 0: // Tracks tab
return mArtistTracksFragment;
case 1: // Either similar, or biography tab
if (mHasRosetta) {
return mArtistSimilarFragment;
} else {
return mArtistInfoFragment;
}
case 2: // Biography tab in case similar is enabled
return mArtistInfoFragment;
}
// should never happen
throw new IllegalStateException("We should never be here, i=" + i);
}
/**
* {@inheritDoc}
*/
@Override
public CharSequence getPageTitle(int position) {
if (position == 0) {
return getString(R.string.tracks).toUpperCase();
} else if (position == 1) {
if (mHasRosetta) {
return getString(R.string.Similar).toUpperCase();
} else {
return getString(R.string.biography).toUpperCase();
}
} else if (position == 2) {
return getString(R.string.biography).toUpperCase();
}
return "Error";
}
/**
* {@inheritDoc}
*/
@Override
public int getCount() {
// Similar only works if we have one provider supporting Rosetta Stone
if (mHasRosetta) {
return FRAGMENT_COUNT;
} else {
return FRAGMENT_COUNT - 1;
}
}
}
/**
* Album art helper for each entry in the Tracks view
*/
private static class AlbumArtLoadListener implements AlbumArtImageView.OnArtLoadedListener {
private View mRootView;
private Handler mHandler;
public AlbumArtLoadListener(View rootView) {
mRootView = rootView;
mHandler = new Handler();
}
/**
* {@inheritDoc}
*/
@Override
public void onArtLoaded(AlbumArtImageView view, BitmapDrawable drawable) {
if (drawable == null || drawable.getBitmap() == null) {
return;
}
Palette.from(drawable.getBitmap()).generate(new Palette.PaletteAsyncListener() {
@Override
public void onGenerated(Palette palette) {
Palette.Swatch vibrant = palette.getVibrantSwatch();
if (vibrant != null && mRootView != null) {
mRootView.setBackgroundColor(vibrant.getRgb());
float luminance = vibrant.getHsl()[2];
final TextView tvArtist = (TextView) mRootView.findViewById(R.id.tvArtistSuggestionArtist);
final TextView tvTitle = (TextView) mRootView.findViewById(R.id.tvArtistSuggestionTitle);
final Button btnPlay = (Button) mRootView.findViewById(R.id.btnArtistSuggestionPlay);
final int color = luminance < 0.6f ? 0xFFFFFFFF : 0xFF333333;
mHandler.post(new Runnable() {
@Override
public void run() {
tvArtist.setTextColor(color);
tvTitle.setTextColor(color);
btnPlay.setTextColor(color);
}
});
}
}
});
}
}
/**
* Click listener on Album group entries
*/
private static class AlbumGroupClickListener implements View.OnClickListener {
private Album mAlbum;
private LinearLayout mContainer;
private LinearLayout mItemHost;
private View mHeader;
private boolean mOpen;
private View mHeaderDivider;
private View mLastItemDivider;
private ParallaxScrollView mRootView;
private Context mContext;
private ArtistTracksFragment mTracksFragment;
private Handler mHandler;
public AlbumGroupClickListener(Album a, ParallaxScrollView rootView,
LinearLayout container, View header,
ArtistTracksFragment tracksFragment) {
mAlbum = a;
mRootView = rootView;
mContainer = container;
mOpen = false;
mHeader = header;
mContext = header.getContext();
mTracksFragment = tracksFragment;
mHandler = new Handler();
mHeaderDivider = header.findViewById(R.id.divider);
mHeaderDivider.setVisibility(View.VISIBLE);
mHeaderDivider.setAlpha(0.0f);
}
/**
* {@inheritDoc}
*/
@Override
public void onClick(View view) {
toggle();
}
/**
* Toggles an album group visibility
*/
public void toggle() {
if (mOpen) {
mItemHost.startAnimation(Utils.animateExpand(mItemHost, false));
mOpen = false;
mLastItemDivider.animate().alpha(0.0f).setDuration(ANIMATION_DURATION)
.setInterpolator(mInterpolator).start();
mHeaderDivider.animate().alpha(0.0f).setDuration(ANIMATION_DURATION)
.setInterpolator(mInterpolator).start();
} else {
if (mItemHost == null) {
mItemHost = new LinearLayout(mContext);
mItemHost.setOrientation(LinearLayout.VERTICAL);
// We insert the view below the group
int index = ((LinearLayout) mHeader.getParent()).indexOfChild(mHeader);
mContainer.addView(mItemHost, index + 1);
mTracksFragment.showAlbumTracks(mAlbum, mItemHost);
// Add the divider at the end
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mLastItemDivider = inflater.inflate(R.layout.divider, mItemHost, false);
mItemHost.addView(mLastItemDivider);
}
mItemHost.startAnimation(Utils.animateExpand(mItemHost, true));
mHeaderDivider.animate().alpha(1.0f).setDuration(ANIMATION_DURATION)
.setInterpolator(mInterpolator).start();
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mRootView.smoothScrollBy(0, Utils.dpToPx(mContext.getResources(), 240));
}
}, 300);
mOpen = true;
}
}
}
/**
* Default constructor
*/
public ArtistFragment() {
mFabShouldResume = false;
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void notifyClosing() {
Utils.animateScale(mFabPlay, true, false);
final TextView tvArtist = (TextView) mRootView.findViewById(R.id.tvArtist);
final PagerTabStrip strip = (PagerTabStrip) mRootView.findViewById(R.id.pagerArtistStrip);
if (!Utils.hasLollipop()) {
tvArtist.animate().alpha(0.0f).setStartDelay(0).setDuration(ArtistActivity.BACK_DELAY).start();
}
strip.animate().alpha(0.0f).setStartDelay(0).translationY(-20).setDuration(ArtistActivity.BACK_DELAY).start();
}
public void scrollToTop() {
mHandler.post(new Runnable() {
@Override
public void run() {
mRootView.smoothScrollTo(0, 0);
}
});
}
/**
* Returns a view in the fragment
*
* @param id The layout item ID
* @return The view if found, null otherwise
*/
public View findViewById(int id) {
return mRootView.findViewById(id);
}
/**
* Sets the main arguments for the fragment
*
* @param hero The hero header image bitmap
* @param extras The intent bundle extras
*/
public void setArguments(Bitmap hero, Bundle extras) {
mHeroImage = hero;
mBackgroundColor = extras.getInt(ArtistActivity.EXTRA_BACKGROUND_COLOR, 0xFF333333);
final String artistRef = extras.getString(ArtistActivity.EXTRA_ARTIST);
final ProviderIdentifier provider = extras.getParcelable(ArtistActivity.EXTRA_PROVIDER);
mArtist = ProviderAggregator.getDefault().retrieveArtist(artistRef, provider);
if (mArtist == null) {
Log.e(TAG, "No cache entry or provider hit for " + artistRef + "!");
throw new IllegalStateException("Artist is null in ArtistFragment arguments!");
}
// Prepare the palette to colorize the FAB
if (mHeroImage != null) {
generateHeroPalette();
}
}
private void generateHeroPalette() {
if (mHeroImage != null && !mHeroImage.isRecycled()) {
Palette.from(mHeroImage).generate(new Palette.PaletteAsyncListener() {
@Override
public void onGenerated(final Palette palette) {
final Palette.Swatch normalColor = palette.getDarkMutedSwatch();
if (normalColor != null && mRootView != null) {
mHandler.post(new Runnable() {
@Override
public void run() {
if (mRootView != null) {
final Palette.Swatch pressedColor = palette.getDarkVibrantSwatch();
mFabPlay.setNormalColor(normalColor.getRgb());
if (pressedColor != null) {
mFabPlay.setPressedColor(pressedColor.getRgb());
} else {
mFabPlay.setPressedColor(normalColor.getRgb());
}
}
}
});
}
}
});
}
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
/**
* {@inheritDoc}
*/
@Override
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mHandler = new Handler();
// Setup the inside fragments
mArtistTracksFragment = new ArtistTracksFragment();
mArtistTracksFragment.setParentFragment(this);
mArtistInfoFragment = new ArtistInfoFragment();
mArtistInfoFragment.setArguments(mArtist);
mArtistSimilarFragment = new ArtistSimilarFragment();
mArtistSimilarFragment.setArguments(mArtist);
// Inflate the main fragment view
mRootView = (ParallaxScrollView) inflater.inflate(R.layout.fragment_artist, container, false);
// Set the hero image and artist from arguments
mHeroImageView = (ImageView) mRootView.findViewById(R.id.ivHero);
if (mHeroImage != null) {
mHeroImageView.setImageBitmap(mHeroImage);
// The hero image that comes from a transition might be low in quality, so load
// the higher quality and fade it in
loadArt(false);
} else {
// Display placeholder and try to get the real art
mHeroImageView.setImageResource(R.drawable.album_placeholder);
loadArt(true);
}
final TextView tvArtist = (TextView) mRootView.findViewById(R.id.tvArtist);
tvArtist.setBackgroundColor(mBackgroundColor);
tvArtist.setText(mArtist.getName());
final PagerTabStrip strip = (PagerTabStrip) mRootView.findViewById(R.id.pagerArtistStrip);
strip.setDrawFullUnderline(false);
strip.setAlpha(0.0f);
strip.setTranslationY(-20);
strip.animate().alpha(1.0f).setDuration(ANIMATION_DURATION).setStartDelay(500).translationY(0).start();
if (!Utils.hasLollipop()) {
tvArtist.setAlpha(0);
tvArtist.animate().alpha(1).setDuration(ANIMATION_DURATION).setStartDelay(500).start();
}
// Setup the subfragments pager
final WrapContentHeightViewPager pager = (WrapContentHeightViewPager) mRootView.findViewById(R.id.pagerArtist);
pager.setAdapter(new ViewPagerAdapter(getChildFragmentManager()));
pager.setOffscreenPageLimit(FRAGMENT_COUNT);
pager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int i) {
if (mRootView.getScrollY() > tvArtist.getTop()) {
mRootView.smoothScrollTo(0, tvArtist.getTop());
}
mHandler.post(new Runnable() {
@Override
public void run() {
pager.setMinimumHeight(500);
pager.requestLayout();
}
});
boolean hasRosetta = ProviderAggregator.getDefault().getRosettaStonePrefix().size() > 0;
if (hasRosetta) {
if (i == FRAGMENT_ID_BIOGRAPHY) {
mArtistInfoFragment.notifyActive();
} else if (i == FRAGMENT_ID_SIMILAR) {
mArtistSimilarFragment.notifyActive();
}
} else {
if (i == FRAGMENT_ID_SIMILAR) {
// This is actually BIOGRAPHY if rosetta is not available
mArtistInfoFragment.notifyActive();
}
}
}
@Override
public void onPageScrollStateChanged(int i) {
}
});
mRootView.setOnScrollListener(new ObservableScrollView.ScrollViewListener() {
@Override
public void onScroll(ObservableScrollView scrollView, int x, int y, int oldx, int oldy) {
final ActionBar ab = ((AppActivity) getActivity()).getSupportActionBar();
if (ab != null) {
if (y >= tvArtist.getTop()) {
ab.hide();
} else {
ab.show();
}
}
}
});
// Setup the source logo
final ImageView ivSource = (ImageView) mRootView.findViewById(R.id.ivSourceLogo);
mLogoBitmap = PluginsLookup.getDefault().getCachedLogo(getResources(), mArtist);
ivSource.setImageDrawable(mLogoBitmap);
// Outline is required for the FAB shadow to be actually oval
mFabPlay = (FloatingActionButton) mRootView.findViewById(R.id.fabPlay);
showFab(false, false);
// Set the FAB animated drawable
mFabDrawable = new PlayPauseDrawable(getResources(), 1);
mFabDrawable.setShape(PlayPauseDrawable.SHAPE_PLAY);
mFabDrawable.setYOffset(6);
final Song currentTrack = PlaybackProxy.getCurrentTrack();
if (currentTrack != null && currentTrack.getArtist() != null
&& currentTrack.getArtist().equals(mArtist.getRef())) {
int state = PlaybackProxy.getState();
if (state == PlaybackService.STATE_PLAYING) {
mFabDrawable.setShape(PlayPauseDrawable.SHAPE_PAUSE);
} else if (state == PlaybackService.STATE_PAUSED) {
mFabShouldResume = true;
} else if (state == PlaybackService.STATE_BUFFERING) {
mFabDrawable.setShape(PlayPauseDrawable.SHAPE_PLAY);
mFabDrawable.setBuffering(true);
mFabShouldResume = true;
} else if (state == PlaybackService.STATE_PAUSING) {
mFabDrawable.setShape(PlayPauseDrawable.SHAPE_PAUSE);
mFabDrawable.setBuffering(true);
mFabShouldResume = true;
}
}
mFabPlay.setImageDrawable(mFabDrawable);
mFabPlay.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mFabDrawable.getCurrentShape() == PlayPauseDrawable.SHAPE_PLAY) {
if (mFabShouldResume) {
PlaybackProxy.play();
} else {
mArtistTracksFragment.playRecommendation();
}
} else {
mFabDrawable.setShape(PlayPauseDrawable.SHAPE_PAUSE);
mFabShouldResume = true;
PlaybackProxy.pause();
}
}
});
return mRootView;
}
/**
* {@inheritDoc}
*/
@Override
public void onResume() {
super.onResume();
// Register for updates
PlaybackProxy.addCallback(mPlaybackCallback);
ProviderAggregator.getDefault().addUpdateCallback(this);
}
/**
* {@inheritDoc}
*/
@Override
public void onPause() {
super.onPause();
mHandler.removeCallbacks(mUpdateAlbumsRunnable);
// Unregister callbacks
PlaybackProxy.removeCallback(mPlaybackCallback);
ProviderAggregator.getDefault().removeUpdateCallback(this);
}
/**
* Shows or hides the play Floating Action Button
*
* @param animate Whether or not to animate the transition
* @param visible Whether or not to display the button
*/
private void showFab(final boolean animate, final boolean visible) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
Utils.animateScale(mFabPlay, animate, visible);
}
});
}
/**
* Sets the play FAB drawable shape
*
* @param shape The shape to display (one of {@link com.fastbootmobile.encore.app.ui.PlayPauseDrawable}
* constants
*/
private void setFabShape(int shape) {
mFabDrawable.setShape(shape);
}
/**
* Sets whether or not tapping the FAB in "Play" shape will start playing from scratch or resume
* the current playback.
*
* @param shouldResume True to resume, false to play from scratch
*/
private void setFabShouldResume(boolean shouldResume) {
mFabShouldResume = shouldResume;
}
/**
* @return The artist displayed by this fragment
*/
public Artist getArtist() {
return mArtist;
}
private void loadArt(final boolean materialTransition) {
AlbumArtHelper.retrieveAlbumArt(getResources(), new AlbumArtHelper.AlbumArtListener() {
@Override
public void onArtLoaded(RecyclingBitmapDrawable output, BoundEntity request) {
try {
if (output != null && !isDetached()) {
mHeroImage = output.getBitmap();
if (materialTransition) {
MaterialTransitionDrawable mtd = new MaterialTransitionDrawable(
(BitmapDrawable) getResources().getDrawable(R.drawable.ic_cloud_offline),
(BitmapDrawable) getResources().getDrawable(R.drawable.album_placeholder));
mtd.transitionTo(output);
mHeroImageView.setImageDrawable(mtd);
} else {
final TransitionDrawable transition = new TransitionDrawable(new Drawable[]{
mHeroImageView.getDrawable(),
output
});
// Make sure the transition happens after the activity animation is done,
// otherwise weird sliding occurs.
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mHeroImageView.setImageDrawable(transition);
transition.startTransition(500);
}
}, 600);
}
generateHeroPalette();
}
} catch (IllegalStateException ignore) {
// We might have left the activity, so go on
}
}
}, mArtist, -1, false);
}
/**
* {@inheritDoc}
*/
@Override
public void onSongUpdate(List<Song> s) {
boolean hasThisArtist = false;
for (Song song : s) {
if (mArtist.getRef().equals(song.getArtist())) {
hasThisArtist = true;
break;
}
}
if (hasThisArtist) {
// TODO: Instead of updating everything, update only the relevant entries
mHandler.removeCallbacks(mUpdateAlbumsRunnable);
mHandler.postDelayed(mUpdateAlbumsRunnable, 300);
}
}
/**
* {@inheritDoc}
*/
@Override
public void onAlbumUpdate(List<Album> a) {
boolean hasThisArtist = false;
final ProviderAggregator aggregator = ProviderAggregator.getDefault();
for (Album album : a) {
Iterator<String> songs = album.songs();
while (songs.hasNext()) {
String songRef = songs.next();
Song song = aggregator.retrieveSong(songRef, album.getProvider());
if (song != null && mArtist != null && mArtist.getRef().equals(song.getArtist())) {
hasThisArtist = true;
break;
}
}
if (hasThisArtist) {
break;
}
}
if (hasThisArtist) {
// TODO: Instead of updating everything, update only the relevant entries
mHandler.removeCallbacks(mUpdateAlbumsRunnable);
mHandler.postDelayed(mUpdateAlbumsRunnable, 300);
}
}
@Override
public void onPlaylistUpdate(List<Playlist> p) {
}
@Override
public void onPlaylistRemoved(String ref) {
}
@Override
public void onArtistUpdate(final List<Artist> a) {
if (a.contains(mArtist)) {
mHandler.removeCallbacks(mUpdateAlbumsRunnable);
mHandler.postDelayed(mUpdateAlbumsRunnable, 300);
}
mArtistSimilarFragment.notifyArtistUpdate(a);
}
@Override
public void onProviderConnected(IMusicProvider provider) {
}
@Override
public void onSearchResult(List<SearchResult> searchResult) {
}
/**
* Class representing the inner fragment displaying albums and tracks
*/
public static class ArtistTracksFragment extends Fragment {
private Song mRecommendedSong;
private boolean mRecommendationLoaded = false;
private View mRootView;
private ArtistFragment mParent;
private HashMap<Song, View> mSongToViewMap = new HashMap<>();
private HashMap<String, View> mAlbumToViewMap = new HashMap<>();
private View mPreviousSongGroup;
private View mPreviousAlbumGroup;
private TextView mOfflineView;
private Handler mHandler;
private Comparator<Album> mComparator = new Comparator<Album>() {
@Override
public int compare(Album album, Album album2) {
if (album.getYear() != album2.getYear()) {
return album.getYear() < album2.getYear() ? 1 : -1;
} else if (album.getName() != null && album2.getName() != null) {
return album.getName().compareTo(album2.getName());
} else {
return album.getRef().compareTo(album2.getRef());
}
}
};
private View.OnClickListener mPlayAlbumClickListener = new View.OnClickListener() {
@Override
public void onClick(View view) {
final ProviderAggregator aggregator = ProviderAggregator.getDefault();
final AlbumViewHolder tag = (AlbumViewHolder) view.getTag();
final Album album = tag.album;
final PlayPauseDrawable drawable = (PlayPauseDrawable) tag.ivPlayAlbum.getDrawable();
if (mPreviousAlbumGroup != null) {
ImageView ivPlayAlbum = (ImageView) mPreviousAlbumGroup.findViewById(R.id.ivPlayAlbum);
PlayPauseDrawable existingDrawable = (PlayPauseDrawable) ivPlayAlbum.getDrawable();
existingDrawable.setShape(PlayPauseDrawable.SHAPE_PLAY);
}
mPreviousAlbumGroup = tag.vRoot;
if (drawable.getRequestedShape() == PlayPauseDrawable.SHAPE_STOP) {
drawable.setShape(PlayPauseDrawable.SHAPE_PLAY);
mParent.setFabShape(PlayPauseDrawable.SHAPE_PLAY);
PlaybackProxy.pause();
} else {
drawable.setShape(PlayPauseDrawable.SHAPE_STOP);
mParent.setFabShape(PlayPauseDrawable.SHAPE_PAUSE);
PlaybackProxy.playAlbum(album);
// Bold the corresponding track
final Iterator<String> songs = album.songs();
if (songs.hasNext()) {
final String songRef = songs.next();
final Song s = aggregator.retrieveSong(songRef, album.getProvider());
boldPlayingTrack(s);
}
}
}
};
/**
* View holder for items in this class
*/
private class AlbumViewHolder {
TextView tvAlbumName;
TextView tvAlbumYear;
AlbumArtImageView ivCover;
ImageView ivPlayAlbum;
Album album;
View vRoot;
AlbumViewHolder(View viewRoot) {
vRoot = viewRoot;
tvAlbumName = (TextView) viewRoot.findViewById(R.id.tvAlbumName);
tvAlbumYear = (TextView) viewRoot.findViewById(R.id.tvAlbumYear);
ivCover = (AlbumArtImageView) viewRoot.findViewById(R.id.ivCover);
ivPlayAlbum = (ImageView) viewRoot.findViewById(R.id.ivPlayAlbum);
}
}
private View.OnClickListener mSongClickListener = new View.OnClickListener() {
@Override
public void onClick(View view) {
final ProviderAggregator aggregator = ProviderAggregator.getDefault();
final Song song = (Song) view.getTag();
Album album = null;
if (song.getAlbum() != null) {
album = aggregator.retrieveAlbum(song.getAlbum(), song.getProvider());
}
if (Utils.canPlaySong(song)) {
// Queue the full album, if possible
PlaybackProxy.clearQueue();
if (album != null) {
PlaybackProxy.queueAlbum(album, false);
// Find the clicked song index and play it
Iterator<String> songs = album.songs();
int i = 0;
while (songs.hasNext()) {
String songRef = songs.next();
if (songRef.equals(song.getRef())) {
break;
} else {
++i;
}
}
PlaybackProxy.playAtIndex(i);
} else {
// We have no album information, just play the song
PlaybackProxy.playSong(song);
}
// Update UI
boldPlayingTrack(song);
updatePlayingAlbum(song.getAlbum());
}
}
};
/**
* {@inheritDoc}
*/
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mRootView = inflater.inflate(R.layout.fragment_artist_tracks, container, false);
mHandler = new Handler();
mOfflineView = (TextView) mRootView.findViewById(R.id.tvErrorMessage);
mOfflineView.setText(R.string.error_artist_unavailable_offline);
mOfflineView.setVisibility(View.GONE);
return mRootView;
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Load recommendation and albums for the first
mHandler.post(new Runnable() {
@Override
public void run() {
loadRecommendation();
loadAlbums(true);
}
});
}
/**
* Sets the parent fragment
*
* @param parent The parent fragment
*/
public void setParentFragment(ArtistFragment parent) {
mParent = parent;
}
/**
* Sets whether or not to show the loading spinner
*
* @param show True to show, false otherwise
*/
private void showLoadingSpinner(final boolean show) {
final ProgressBar pb = (ProgressBar) mRootView.findViewById(R.id.pbArtistLoading);
if (show && pb.getVisibility() != View.VISIBLE
|| !show && pb.getVisibility() != View.GONE) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
pb.setVisibility(show ? View.VISIBLE : View.GONE);
}
});
}
}
/**
* Play the artist radio
*/
private void playRecommendation() {
if (mRecommendationLoaded && mRecommendedSong != null) {
// Generate radio tracks
List<Song> tracks = Suggestor.getInstance().buildArtistRadio(mParent.getArtist());
PlaybackProxy.clearQueue();
// Add the recommended song itself first, then the generated tracks
PlaybackProxy.queueSong(mRecommendedSong, true);
for (Song song : tracks) {
PlaybackProxy.queueSong(song, false);
}
// And play!
PlaybackProxy.playAtIndex(0);
mParent.setFabShape(PlayPauseDrawable.SHAPE_PAUSE);
mParent.setFabShouldResume(true);
// Update UI indicators
boldPlayingTrack(mRecommendedSong);
updatePlayingAlbum(mRecommendedSong.getAlbum());
}
}
/**
* Load the recommended track
*/
private synchronized void loadRecommendation() {
if (mRecommendationLoaded || mParent == null || mRootView == null) {
// Already loaded or parent not loaded yet or view not loaded yet
return;
}
final ProviderAggregator aggregator = ProviderAggregator.getDefault();
// Load a song from the Suggestor and display it
Song recommended = Suggestor.getInstance().suggestBestForArtist(mParent.getArtist());
if (recommended != null) {
mRecommendedSong = recommended;
Album album = aggregator.retrieveAlbum(recommended.getAlbum(), recommended.getProvider());
CardView cvRec = (CardView) mRootView.findViewById(R.id.cardArtistSuggestion);
TextView tvTitle = (TextView) mRootView.findViewById(R.id.tvArtistSuggestionTitle);
TextView tvArtist = (TextView) mRootView.findViewById(R.id.tvArtistSuggestionArtist);
Button btnPlayNow = (Button) mRootView.findViewById(R.id.btnArtistSuggestionPlay);
tvTitle.setText(recommended.getTitle());
if (album != null) {
tvArtist.setText(getString(R.string.from_the_album, album.getName()));
} else {
tvArtist.setText("");
}
AlbumArtImageView ivCov = (AlbumArtImageView) mRootView.findViewById(R.id.ivArtistSuggestionCover);
ivCov.setOnArtLoadedListener(new AlbumArtLoadListener(cvRec));
if (recommended.getAlbum() != null) {
ivCov.loadArtForAlbum(aggregator.retrieveAlbum(recommended.getAlbum(), recommended.getProvider()));
}
// If we were gone, animate in
if (cvRec.getVisibility() == View.GONE) {
cvRec.setVisibility(View.VISIBLE);
cvRec.setAlpha(0.0f);
cvRec.animate().alpha(1.0f).setDuration(ANIMATION_DURATION)
.setInterpolator(mInterpolator).start();
View suggestionTitle = mRootView.findViewById(R.id.tvArtistSuggestionNote);
suggestionTitle.setVisibility(View.VISIBLE);
suggestionTitle.setAlpha(0.0f);
suggestionTitle.animate().alpha(1.0f).setDuration(ANIMATION_DURATION)
.setInterpolator(mInterpolator).start();
}
btnPlayNow.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
playRecommendation();
}
});
mRecommendationLoaded = true;
} else {
mRootView.findViewById(R.id.cardArtistSuggestion).setVisibility(View.GONE);
mRootView.findViewById(R.id.tvArtistSuggestionNote).setVisibility(View.GONE);
mRecommendationLoaded = false;
}
}
/**
* Displays a toast
*
* @param string A string resource of the text to display
*/
private void postToast(@StringRes final int string) {
mHandler.post(new Runnable() {
@Override
public void run() {
Utils.shortToast(getActivity(), string);
}
});
}
/**
* Fetch the albums from the provider
*/
private void fetchAlbums() {
new Thread() {
public void run() {
ProviderIdentifier pi = mParent.getArtist().getProvider();
ProviderConnection pc = PluginsLookup.getDefault().getProvider(pi);
if (pc != null) {
IMusicProvider provider = pc.getBinder();
if (provider != null) {
try {
boolean hasMore = provider.fetchArtistAlbums(mParent.getArtist().getRef());
showLoadingSpinner(hasMore && !ProviderAggregator.getDefault().isOfflineMode());
mParent.showFab(true, !hasMore);
} catch (RemoteException e) {
Log.e(TAG, "Unable to fetch artist albums", e);
postToast(R.string.plugin_error);
}
} else {
showLoadingSpinner(false);
mParent.showFab(true, true);
Log.e(TAG, "Provider is null, cannot fetch albums");
postToast(R.string.plugin_error);
}
} else {
showLoadingSpinner(false);
mParent.showFab(true, true);
Log.e(TAG, "ProviderConnection is null, cannot fetch albums");
postToast(R.string.plugin_error);
}
}
}.start();
}
/**
* Load and display albums
*
* @param request Whether or not to fetch albums from the provider
*/
private void loadAlbums(final boolean request) {
if (request) {
// Make sure we loaded all the albums for that artist
fetchAlbums();
}
if (mRootView == null && mHandler != null) {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
loadAlbums(request);
}
}, 200);
return;
}
// Check if we're offline, and if we have nothing to show, then show the offline error
final ProviderAggregator aggregator = ProviderAggregator.getDefault();
final LinearLayout llAlbums = (LinearLayout) mRootView.findViewById(R.id.llAlbums);
llAlbums.removeAllViews();
Iterator<String> albumIt = new ArrayList<>(mParent.getArtist().getAlbums()).iterator();
List<Album> albums = new ArrayList<>();
if (aggregator.isOfflineMode() && !albumIt.hasNext()) {
mOfflineView.setVisibility(View.VISIBLE);
mRootView.findViewById(R.id.tvHeaderAlbums).setVisibility(View.GONE);
} else {
mOfflineView.setVisibility(View.GONE);
mRootView.findViewById(R.id.tvHeaderAlbums).setVisibility(View.VISIBLE);
while (albumIt.hasNext()) {
String albumRef = albumIt.next();
Album album = aggregator.retrieveAlbum(albumRef, mParent.getArtist().getProvider());
if (album != null) {
ProviderConnection conn = PluginsLookup.getDefault().getProvider(album.getProvider());
if (conn != null) {
IMusicProvider provider = conn.getBinder();
try {
if (provider != null) {
provider.fetchAlbumTracks(albumRef);
}
} catch (RemoteException e) {
Log.e(TAG, "Remote exception while trying to fetch album tracks", e);
}
albums.add(album);
}
}
}
}
// Sort it from album names
try {
Collections.sort(albums, mComparator);
} catch (IllegalArgumentException ignore) {
}
// Then inflate views
final LayoutInflater inflater = getActivity().getLayoutInflater();
for (final Album album : albums) {
AlbumViewHolder holder;
View viewRoot;
if (mAlbumToViewMap.containsKey(album.getRef())) {
viewRoot = mAlbumToViewMap.get(album.getRef());
holder = (AlbumViewHolder) viewRoot.getTag();
holder.album = album;
llAlbums.addView(viewRoot);
AlbumGroupClickListener listener =
new AlbumGroupClickListener(album, mParent.mRootView, llAlbums, viewRoot, this);
viewRoot.setOnClickListener(listener);
} else {
viewRoot = inflater.inflate(R.layout.exp_group_albums, llAlbums, false);
llAlbums.addView(viewRoot);
holder = new AlbumViewHolder(viewRoot);
holder.album = album;
viewRoot.setTag(holder);
holder.ivPlayAlbum.setTag(holder);
AlbumGroupClickListener listener =
new AlbumGroupClickListener(album, mParent.mRootView, llAlbums, viewRoot, this);
viewRoot.setOnClickListener(listener);
final PlayPauseDrawable drawable = new PlayPauseDrawable(getResources(), 1);
drawable.setShape(PlayPauseDrawable.SHAPE_PLAY);
drawable.setColor(0xCC333333);
holder.ivPlayAlbum.setImageDrawable(drawable);
holder.ivPlayAlbum.setOnClickListener(mPlayAlbumClickListener);
}
// If the album is loaded, show its metadata
if (album.isLoaded()) {
holder.tvAlbumName.setText(album.getName());
if (album.getYear() > 0) {
holder.tvAlbumYear.setVisibility(View.VISIBLE);
holder.tvAlbumYear.setText(Integer.toString(album.getYear()));
} else {
holder.tvAlbumYear.setVisibility(View.GONE);
}
holder.ivPlayAlbum.setVisibility(View.VISIBLE);
// Set play or pause based on if this album is playing
int state = PlaybackProxy.getState();
if (state == PlaybackService.STATE_PLAYING) {
Song currentSong = PlaybackProxy.getCurrentTrack();
if (currentSong != null && album.getRef().equals(currentSong.getAlbum())) {
updatePlayingAlbum(currentSong.getAlbum());
}
}
} else {
// Album isn't loaded, show "Loading"
holder.tvAlbumName.setText(getString(R.string.loading));
holder.tvAlbumYear.setVisibility(View.GONE);
holder.ivPlayAlbum.setVisibility(View.GONE);
}
// Load the album art
holder.ivCover.loadArtForAlbum(album);
// Cache the view
mAlbumToViewMap.put(album.getRef(), viewRoot);
}
// Hide loading spinner and show the play FAB as we now have stuff
showLoadingSpinner(false);
mParent.showFab(true, true);
}
/**
* Shows the album tracks of the provided album
*
* @param album The album of which display tracks
* @param container The container in which expand views
*/
private void showAlbumTracks(Album album, LinearLayout container) {
Iterator<String> songsIt = album.songs();
final LayoutInflater inflater = getActivity().getLayoutInflater();
final ProviderAggregator aggregator = ProviderAggregator.getDefault();
while (songsIt.hasNext()) {
final Song song = aggregator.retrieveSong(songsIt.next(), album.getProvider());
if (song == null) {
Log.w(TAG, "Null song in album!");
continue;
}
View itemRoot = inflater.inflate(R.layout.exp_item_albums, container, false);
container.addView(itemRoot);
itemRoot.setTag(song);
// Set alpha based on offline availability and mode
if ((aggregator.isOfflineMode()
&& (song.getOfflineStatus() != BoundEntity.OFFLINE_STATUS_READY)
|| (!song.isAvailable()))) {
Utils.setChildrenAlpha((ViewGroup) itemRoot,
Float.parseFloat(getString(R.string.unavailable_track_alpha)));
} else {
Utils.setChildrenAlpha((ViewGroup) itemRoot, 1.0f);
}
mSongToViewMap.put(song, itemRoot);
TextView tvTrackName = (TextView) itemRoot.findViewById(R.id.tvTrackName);
TextView tvTrackDuration = (TextView) itemRoot.findViewById(R.id.tvTrackDuration);
final ImageView ivOverflow = (ImageView) itemRoot.findViewById(R.id.ivOverflow);
final ImageView ivOffline = (ImageView) itemRoot.findViewById(R.id.ivOffline);
ivOffline.setVisibility(View.VISIBLE);
switch (song.getOfflineStatus()) {
case BoundEntity.OFFLINE_STATUS_DOWNLOADING:
ivOffline.setImageResource(R.drawable.ic_sync_in_progress);
break;
case BoundEntity.OFFLINE_STATUS_ERROR:
ivOffline.setImageResource(R.drawable.ic_sync_problem);
break;
case BoundEntity.OFFLINE_STATUS_NO:
ivOffline.setVisibility(View.GONE);
break;
case BoundEntity.OFFLINE_STATUS_READY:
ivOffline.setImageResource(R.drawable.ic_track_downloaded);
break;
case BoundEntity.OFFLINE_STATUS_PENDING:
ivOffline.setImageResource(R.drawable.ic_track_download_pending);
break;
}
if (song.isLoaded()) {
tvTrackName.setText(song.getTitle());
tvTrackDuration.setText(Utils.formatTrackLength(song.getDuration()));
ivOverflow.setVisibility(View.VISIBLE);
// Set song click listener if playable
if (song.isAvailable()) {
itemRoot.setOnClickListener(mSongClickListener);
}
// Set overflow popup
ivOverflow.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Utils.showSongOverflow(getActivity(), ivOverflow, song, true);
}
});
// Bold if already playing
int state = PlaybackProxy.getState();
if (state == PlaybackService.STATE_PLAYING) {
Song currentSong = PlaybackProxy.getCurrentTrack();
if (currentSong != null && song.getRef().equals(currentSong.getRef())) {
boldPlayingTrack(currentSong);
}
}
} else {
tvTrackName.setText(getString(R.string.loading));
tvTrackDuration.setText("");
ivOverflow.setVisibility(View.GONE);
}
}
}
/**
* Updates the current playing album
*
* @param albumRef The new album being played
*/
private void updatePlayingAlbum(String albumRef) {
View view = mAlbumToViewMap.get(albumRef);
ImageView ivPlayAlbum;
if (mPreviousAlbumGroup != null) {
ivPlayAlbum = (ImageView) mPreviousAlbumGroup.findViewById(R.id.ivPlayAlbum);
PlayPauseDrawable drawable = (PlayPauseDrawable) ivPlayAlbum.getDrawable();
drawable.setShape(PlayPauseDrawable.SHAPE_PLAY);
}
if (view != null) {
ivPlayAlbum = (ImageView) view.findViewById(R.id.ivPlayAlbum);
PlayPauseDrawable drawable = (PlayPauseDrawable) ivPlayAlbum.getDrawable();
drawable.setShape(PlayPauseDrawable.SHAPE_STOP);
}
mPreviousAlbumGroup = view;
}
/**
* Finds and bolds the track that is currently playing, if we have it
*
* @param s The song that is currently playing
*/
private void boldPlayingTrack(Song s) {
View view = mSongToViewMap.get(s);
TextView tvTrackName, tvTrackDuration;
if (mPreviousSongGroup != null) {
tvTrackName = (TextView) mPreviousSongGroup.findViewById(R.id.tvTrackName);
tvTrackDuration = (TextView) mPreviousSongGroup.findViewById(R.id.tvTrackDuration);
tvTrackName.setTypeface(null, Typeface.NORMAL);
tvTrackDuration.setTypeface(null, Typeface.NORMAL);
}
if (view != null) {
tvTrackName = (TextView) view.findViewById(R.id.tvTrackName);
tvTrackDuration = (TextView) view.findViewById(R.id.tvTrackDuration);
tvTrackName.setTypeface(null, Typeface.BOLD);
tvTrackDuration.setTypeface(null, Typeface.BOLD);
}
mPreviousSongGroup = view;
}
}
/**
* Fragment showing the Artist's biography
*/
public static class ArtistInfoFragment extends Fragment {
private static Artist mArtist;
private Handler mHandler;
private TextView mArtistInfo;
private TextView mOfflineView;
private boolean mInfoLoaded;
private ProgressBar mLoadingSpinner;
/**
* Default constructor
*/
public ArtistInfoFragment() {
mHandler = new Handler();
mInfoLoaded = false;
}
/**
* Sets the active artist for which get the biography
*
* @param artist The artist displayed
*/
public void setArguments(Artist artist) {
mArtist = artist;
}
/**
* Notifies that the fragment is currently active, and that data should be populated.
*/
public void notifyActive() {
if (!mInfoLoaded) {
if (ProviderAggregator.getDefault().isOfflineMode()) {
mOfflineView.setVisibility(View.VISIBLE);
mLoadingSpinner.setVisibility(View.GONE);
} else {
mOfflineView.setVisibility(View.GONE);
mLoadingSpinner.setVisibility(View.VISIBLE);
mInfoLoaded = true;
new Thread() {
public void run() {
loadBiographySync();
}
}.start();
}
}
}
/**
* Loads synchronously the biography data
*/
private void loadBiographySync() {
final EchoNest echoNest = new EchoNest();
try {
com.echonest.api.v4.Artist enArtist = echoNest.searchArtistByName(mArtist.getName());
if (enArtist != null) {
final Biography bio = echoNest.getArtistBiography(enArtist);
mHandler.post(new Runnable() {
@Override
public void run() {
if (mLoadingSpinner != null && mArtistInfo != null && !isDetached()) {
mLoadingSpinner.setVisibility(View.GONE);
if (bio != null) {
mArtistInfo.setText(getString(R.string.biography_format,
bio.getText(), bio.getSite(), bio.getURL(),
bio.getLicenseType(), bio.getLicenseAttribution()));
} else {
mArtistInfo.setText(getString(R.string.no_bio_available));
}
}
}
});
}
} catch (Exception e) {
Log.e(TAG, "Unable to get artist information", e);
mHandler.post(new Runnable() {
@Override
public void run() {
Utils.shortToast(getActivity(), R.string.unable_fetch_artist_info);
}
});
}
}
/**
* {@inheritDoc}
*/
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_artist_info, container, false);
mArtistInfo = (TextView) rootView.findViewById(R.id.tvArtistInfo);
mLoadingSpinner = (ProgressBar) rootView.findViewById(R.id.pbArtistInfo);
mOfflineView = (TextView) rootView.findViewById(R.id.tvErrorMessage);
mOfflineView.setText(R.string.error_biography_unavailable_offline);
return rootView;
}
}
/**
* Fragment showing similar artists
*/
public static class ArtistSimilarFragment extends Fragment {
private RecyclerView mArtistsGrid;
private Artist mArtist;
private boolean mSimilarLoaded;
private ArtistsAdapter mAdapter;
private Handler mHandler;
private List<Artist> mSimilarArtists;
private ProgressBar mArtistsSpinner;
private TextView mOfflineView;
/**
* Default constructor
*/
public ArtistSimilarFragment() {
mAdapter = new ArtistsAdapter();
mHandler = new Handler();
mSimilarArtists = new ArrayList<>();
}
/**
* Sets the active artist to display
*
* @param artist The artist displayed
*/
public void setArguments(Artist artist) {
mArtist = artist;
}
/**
* Notify that the fragment is active and that data should be populated
*/
public void notifyActive() {
Log.e(TAG, "Notify Active Similar, similarLoaded=" + mSimilarLoaded);
if (!mSimilarLoaded) {
if (ProviderAggregator.getDefault().isOfflineMode()) {
if (mOfflineView != null) mOfflineView.setVisibility(View.VISIBLE);
if (mArtistsSpinner != null) mArtistsSpinner.setVisibility(View.GONE);
} else {
if (mOfflineView != null) mOfflineView.setVisibility(View.GONE);
if (mArtistsSpinner != null) mArtistsSpinner.setVisibility(View.VISIBLE);
mSimilarLoaded = true;
new Thread() {
public void run() {
loadSimilarSync();
}
}.start();
}
} else {
ensureSimilar();
}
}
/**
* Called when the host fragment notifies that artists have been updated by a provider
*
* @param artists The artists who got updated
*/
public void notifyArtistUpdate(final List<Artist> artists) {
for (Artist artist : artists) {
final int artistIndex = mSimilarArtists.indexOf(artist);
if (artistIndex >= 0) {
mHandler.post(new Runnable() {
@Override
public void run() {
mAdapter.notifyDataSetChanged();
}
});
}
}
}
/**
* Load synchronously the similar artists
*/
public void loadSimilarSync() {
EchoNest echoNest = new EchoNest();
try {
com.echonest.api.v4.Artist enArtist = echoNest.searchArtistByName(mArtist.getName());
if (enArtist != null) {
List<com.echonest.api.v4.Artist> similars = echoNest.getArtistSimilar(enArtist);
// Retrieve the rosetta stone prefix
String rosettaPreferred = ProviderAggregator.getDefault().getPreferredRosettaStonePrefix();
ProviderIdentifier rosettaProvider = null;
if (rosettaPreferred != null) {
rosettaProvider = ProviderAggregator.getDefault().getRosettaStoneIdentifier(rosettaPreferred);
}
// For each similar artist, get the rosetta stone ID, and add it to the adapter
for (com.echonest.api.v4.Artist similar : similars) {
if (rosettaPreferred != null) {
String ref = echoNest.getArtistForeignID(similar, rosettaPreferred);
if (ref != null) {
Artist artist = ProviderAggregator.getDefault().retrieveArtist(ref, rosettaProvider);
if (artist != null) {
mSimilarArtists.add(artist);
} else {
Log.e(TAG, "Null artist for similar");
}
}
}
}
mHandler.post(new Runnable() {
@Override
public void run() {
ensureSimilar();
}
});
}
} catch (EchoNestException e) {
Log.e(TAG, "Cannot get similar artist", e);
mHandler.post(new Runnable() {
@Override
public void run() {
mOfflineView.setVisibility(View.VISIBLE);
}
});
}
}
private void ensureSimilar() {
mAdapter.addAllUnique(mSimilarArtists);
mAdapter.notifyDataSetChanged();
if (mArtistsGrid != null && mArtistsSpinner != null) {
mArtistsGrid.setAdapter(mAdapter);
mArtistsSpinner.setVisibility(View.GONE);
mArtistsGrid.setVisibility(View.VISIBLE);
mOfflineView.setVisibility(View.GONE);
}
}
/**
* {@inheritDoc}
*/
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_artist_similar, container, false);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
View rootView = getView();
if (rootView != null) {
mArtistsSpinner = (ProgressBar) rootView.findViewById(R.id.pbSimilarArtists);
mArtistsGrid = (RecyclerView) rootView.findViewById(R.id.gridSimilarArtists);
mArtistsGrid.setHasFixedSize(true);
mArtistsGrid.setLayoutManager(new GridLayoutManager(view.getContext(), 2));
mOfflineView = (TextView) rootView.findViewById(R.id.tvErrorMessage);
mOfflineView.setText(R.string.error_similar_unavailable_offline);
}
}
}
}