package com.sregg.android.tv.spotifyPlayer.fragments; import android.content.Context; import android.graphics.Bitmap; import android.os.AsyncTask; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v17.leanback.app.BackgroundManager; import android.support.v17.leanback.app.DetailsFragment; import android.support.v17.leanback.widget.Action; import android.support.v17.leanback.widget.ArrayObjectAdapter; import android.support.v17.leanback.widget.ClassPresenterSelector; import android.support.v17.leanback.widget.DetailsOverviewRow; import android.support.v17.leanback.widget.DetailsOverviewRowPresenter; import android.support.v17.leanback.widget.OnActionClickedListener; import android.support.v17.leanback.widget.OnItemViewClickedListener; import android.support.v17.leanback.widget.Presenter; import android.support.v17.leanback.widget.Row; import android.support.v17.leanback.widget.RowPresenter; import android.support.v7.graphics.Palette; import android.util.DisplayMetrics; import android.util.Log; import com.spotify.sdk.android.player.PlayerState; import com.spotify.sdk.android.player.PlayerStateCallback; import com.squareup.otto.Subscribe; import com.squareup.picasso.Picasso; import com.sregg.android.tv.spotifyPlayer.BusProvider; import com.sregg.android.tv.spotifyPlayer.R; import com.sregg.android.tv.spotifyPlayer.SpotifyTvApplication; import com.sregg.android.tv.spotifyPlayer.controllers.SpotifyPlayerController; import com.sregg.android.tv.spotifyPlayer.events.ContentState; import com.sregg.android.tv.spotifyPlayer.events.PlayerStateChanged; import com.sregg.android.tv.spotifyPlayer.presenters.TracksHeaderRowPresenter; import com.sregg.android.tv.spotifyPlayer.rows.TrackRow; import com.sregg.android.tv.spotifyPlayer.rows.TracksHeaderRow; import com.sregg.android.tv.spotifyPlayer.utils.BlurTransformation; import com.sregg.android.tv.spotifyPlayer.utils.Utils; import java.io.IOException; import java.util.ArrayList; import java.util.List; import kaaes.spotify.webapi.android.models.Album; import kaaes.spotify.webapi.android.models.Playlist; import kaaes.spotify.webapi.android.models.Track; import kaaes.spotify.webapi.android.models.TrackSimple; public abstract class TracksDetailsFragment extends DetailsFragment { protected static final long ACTION_PLAY_PAUSE_PLAYLIST = 1; private static final String TAG = TracksDetailsFragment.class.getSimpleName(); private static final int DETAIL_THUMB_WIDTH = 274; private static final int DETAIL_THUMB_HEIGHT = 274; protected SpotifyPlayerController playerController; protected ArrayObjectAdapter actionsAdapter; private BackgroundManager mBackgroundManager; private DisplayMetrics mMetrics; private ArrayObjectAdapter mRowsAdapter; private DetailsOverviewRowPresenter mDetailsPresenter; private DetailsOverviewRow mDetailsRow; private Object busEventListener; @Override public void onCreate(Bundle savedInstanceState) { Log.d(TAG, "onCreate"); super.onCreate(savedInstanceState); busEventListener = new Object() { @Subscribe public void onPlayerStateChanged(final PlayerStateChanged event) { TracksDetailsFragment.this.onPlayerStateChanged(event); } }; BusProvider.getInstance().register(busEventListener); playerController = SpotifyTvApplication.getInstance().getSpotifyPlayerController(); setupFragment(); setupBackground(); } public SpotifyPlayerController getPlayerController() { return playerController; } @Override public void onDestroy() { super.onDestroy(); BusProvider.getInstance().unregister(busEventListener); } protected abstract Presenter getDetailsPresenter(); protected abstract Presenter getTrackRowPresenter(OnItemViewClickedListener onTrackRowItemClicked); private void setupFragment() { mDetailsPresenter = new DetailsOverviewRowPresenter(getDetailsPresenter()); mDetailsPresenter.setStyleLarge(false); ClassPresenterSelector ps = new ClassPresenterSelector(); ps.addClassPresenter(DetailsOverviewRow.class, mDetailsPresenter); ps.addClassPresenter(TracksHeaderRow.class, new TracksHeaderRowPresenter()); OnItemViewClickedListener onTrackRowItemClicked = new OnItemViewClickedListener() { @Override public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) { if (item instanceof TrackSimple) { playFromTrack(((TrackSimple) item)); } } }; ps.addClassPresenter(TrackRow.class, getTrackRowPresenter(onTrackRowItemClicked)); mRowsAdapter = new ArrayObjectAdapter(ps); setAdapter(mRowsAdapter); setOnActionClickedListener(new OnActionClickedListener() { @Override public void onActionClicked(Action action) { TracksDetailsFragment.this.onActionClicked(action); } }); } /** * Called when an Action has been clicked * * @param action * @return true if the action click has been consumed */ protected boolean onActionClicked(Action action) { if (action.getId() == ACTION_PLAY_PAUSE_PLAYLIST) { if (isContentPlaying()) { playerController.togglePauseResume(); } else { playerController.play(getObjectUri(), getTrackUris(), getTracks()); } return true; } return false; } protected void onContentLoaded() { if (!isAdded()) { return; } playerController.getPlayerState(new PlayerStateCallback() { @Override public void onPlayerState(PlayerState playerState) { getDetailsRow().setItem(getObject()); loadDetailsRowImage(getImageUrl()); setupTracksRows(getTracks()); } }); } private String getImageUrl() { Object item = getObject(); if (item instanceof Album) { Album album = (Album) item; if (album.images.size() > 0) { String imageUrl = album.images.get(0).url; return imageUrl; } } else if (item instanceof Playlist) { Playlist playlist = (Playlist) item; if (playlist.images.size() > 0) { String imageUrl = playlist.images.get(0).url; return imageUrl; } } else if (item instanceof Track) { Track track = (Track) item; if (track.album != null && track.album.images != null && track.album.images.size() > 0) { return track.album.images.get(0).url; } } return null; } private void createDetailsRow() { actionsAdapter = new ArrayObjectAdapter(); populateActionsAdapter(); mDetailsRow = new DetailsOverviewRow(new Object()); mDetailsRow.setActionsAdapter(actionsAdapter); mRowsAdapter.add(0, mDetailsRow); } private void playFromTrack(TrackSimple item) { SpotifyTvApplication.getInstance().getSpotifyPlayerController().play(getObjectUri(), item.uri, getTrackUris(), getTracks()); } @Nullable protected abstract List<TrackSimple> getTracks(); @Nullable protected abstract List<String> getTrackUris(); @Nullable protected abstract String getObjectUri(); protected abstract Object getObject(); private void setupBackground() { mBackgroundManager = BackgroundManager.getInstance(getActivity()); mBackgroundManager.attach(getActivity().getWindow()); mMetrics = new DisplayMetrics(); getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics); } protected void setOnActionClickedListener(OnActionClickedListener listener) { mDetailsPresenter.setOnActionClickedListener(listener); } public DetailsOverviewRow getDetailsRow() { if (mDetailsRow == null) { createDetailsRow(); } return mDetailsRow; } protected void setupTracksRows(List<TrackSimple> tracks) { if (mRowsAdapter.size() < 2) { mRowsAdapter.add(new TracksHeaderRow()); List<TrackRow> trackRows = new ArrayList<>(tracks.size()); for (TrackSimple track : tracks) { trackRows.add(new TrackRow(track)); } mRowsAdapter.addAll(mRowsAdapter.size(), trackRows); } } private void loadDetailsRowImage(String imageUrl) { if (imageUrl == null) { getDetailsRow().setImageBitmap(getActivity(), null); mBackgroundManager.setBitmap(null); } else { new ImageLoader(getActivity()).execute(imageUrl); } } private boolean isContentPlaying() { ContentState currentPlayState = playerController.getPlayingState(); return currentPlayState != null && currentPlayState.isCurrentObject(getObjectUri()); } private void populateActionsAdapter() { boolean playlistPlaying = isContentPlaying(); Action playAction = new Action(ACTION_PLAY_PAUSE_PLAYLIST, playlistPlaying ? getResources().getString(R.string.lb_playback_controls_pause) : getResources().getString(R.string.lb_playback_controls_play), null); actionsAdapter.add(playAction); List<Action> actions = getDetailActions(); if (actions != null) { for (Action action : getDetailActions()) { actionsAdapter.add(action); } } } public void onPlayerStateChanged(PlayerStateChanged playerStateChanged) { PlayerState playerState = playerStateChanged.getPlayerState(); boolean playlistPlaying = isContentPlaying() && playerState.playing; Action action = (Action) actionsAdapter.get(0); action.setLabel1(playlistPlaying ? getResources().getString(R.string.lb_playback_controls_pause) : getResources().getString(R.string.lb_playback_controls_play)); actionsAdapter.notifyArrayItemRangeChanged(0, 1); } abstract List<Action> getDetailActions(); private class ImageLoader extends AsyncTask<String, Void, Void> { private Context context; public ImageLoader(Context context) { this.context = context.getApplicationContext(); } private Bitmap mBackground; @Override protected Void doInBackground(String... params) { if (getDetailsRow() == null || mDetailsPresenter == null || !isAdded()) { return null; } Bitmap cover; try { cover = Picasso.with(this.context) .load(params[0]) .resize( Utils.dpToPx(DETAIL_THUMB_WIDTH, this.context), Utils.dpToPx(DETAIL_THUMB_HEIGHT, this.context) ) .centerCrop() .get(); getDetailsRow().setImageBitmap(this.context, cover); Palette palette = Palette.generate(cover); Palette.Swatch swatch = palette.getDarkVibrantSwatch(); if (swatch == null) { swatch = palette.getDarkMutedSwatch(); } if (swatch != null) { mDetailsPresenter.setBackgroundColor(swatch.getRgb()); } mBackground = Picasso.with(this.context) .load(params[0]) .transform(new BlurTransformation(this.context)) .resize(mMetrics.widthPixels, mMetrics.heightPixels) .centerCrop() .get(); } catch (IOException e) { e.printStackTrace(); } return null; } @Override protected void onPostExecute(Void aVoid) { if (!isAdded()) { return; } mBackgroundManager.setBitmap(mBackground); setAdapter(mRowsAdapter); } } }