package com.kabouzeid.gramophone.ui.fragments.player.card;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.app.Activity;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.ColorInt;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.CardView;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.TextView;
import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.animator.RefactoredDefaultItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager;
import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils;
import com.kabouzeid.appthemehelper.ThemeStore;
import com.kabouzeid.appthemehelper.util.ATHUtil;
import com.kabouzeid.appthemehelper.util.ColorUtil;
import com.kabouzeid.appthemehelper.util.ToolbarContentTintHelper;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.adapter.base.MediaEntryViewHolder;
import com.kabouzeid.gramophone.adapter.song.PlayingQueueAdapter;
import com.kabouzeid.gramophone.dialogs.LyricsDialog;
import com.kabouzeid.gramophone.dialogs.SongShareDialog;
import com.kabouzeid.gramophone.helper.MusicPlayerRemote;
import com.kabouzeid.gramophone.helper.menu.SongMenuHelper;
import com.kabouzeid.gramophone.model.Song;
import com.kabouzeid.gramophone.ui.activities.base.AbsSlidingMusicPanelActivity;
import com.kabouzeid.gramophone.ui.fragments.player.AbsPlayerFragment;
import com.kabouzeid.gramophone.ui.fragments.player.PlayerAlbumCoverFragment;
import com.kabouzeid.gramophone.util.MusicUtil;
import com.kabouzeid.gramophone.util.Util;
import com.kabouzeid.gramophone.util.ViewUtil;
import com.kabouzeid.gramophone.views.WidthFitSquareLayout;
import com.sothree.slidinguppanel.SlidingUpPanelLayout;
import org.jaudiotagger.audio.AudioFileIO;
import org.jaudiotagger.tag.FieldKey;
import java.io.File;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.Unbinder;
public class CardPlayerFragment extends AbsPlayerFragment implements PlayerAlbumCoverFragment.Callbacks, SlidingUpPanelLayout.PanelSlideListener {
public static final String TAG = CardPlayerFragment.class.getSimpleName();
private Unbinder unbinder;
@BindView(R.id.player_toolbar)
Toolbar toolbar;
@BindView(R.id.player_sliding_layout)
SlidingUpPanelLayout slidingUpPanelLayout;
@BindView(R.id.player_recycler_view)
RecyclerView recyclerView;
@BindView(R.id.playing_queue_card)
CardView playingQueueCard;
@BindView(R.id.color_background)
View colorBackground;
@BindView(R.id.player_queue_sub_header)
TextView playerQueueSubHeader;
private int lastColor;
private CardPlayerPlaybackControlsFragment playbackControlsFragment;
private PlayerAlbumCoverFragment playerAlbumCoverFragment;
private LinearLayoutManager layoutManager;
private PlayingQueueAdapter playingQueueAdapter;
private RecyclerView.Adapter wrappedAdapter;
private RecyclerViewDragDropManager recyclerViewDragDropManager;
private AsyncTask updateIsFavoriteTask;
private AsyncTask updateLyricsAsyncTask;
private LyricsDialog.LyricInfo lyricsInfo;
private Impl impl;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (Util.isLandscape(getResources())) {
impl = new LandscapeImpl(this);
} else {
impl = new PortraitImpl(this);
}
View view = inflater.inflate(R.layout.fragment_card_player, container, false);
unbinder = ButterKnife.bind(this, view);
return view;
}
@Override
public void onViewCreated(final View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
impl.init();
setUpPlayerToolbar();
setUpSubFragments();
setUpRecyclerView();
slidingUpPanelLayout.addPanelSlideListener(this);
slidingUpPanelLayout.setAntiDragView(view.findViewById(R.id.draggable_area));
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
impl.setUpPanelAndAlbumCoverHeight();
}
});
// for some reason the xml attribute doesn't get applied here.
playingQueueCard.setCardBackgroundColor(ATHUtil.resolveColor(getActivity(), R.attr.cardBackgroundColor));
}
@Override
public void onDestroyView() {
if (slidingUpPanelLayout != null) {
slidingUpPanelLayout.removePanelSlideListener(this);
}
if (recyclerViewDragDropManager != null) {
recyclerViewDragDropManager.release();
recyclerViewDragDropManager = null;
}
if (recyclerView != null) {
recyclerView.setItemAnimator(null);
recyclerView.setAdapter(null);
recyclerView = null;
}
if (wrappedAdapter != null) {
WrapperAdapterUtils.releaseAll(wrappedAdapter);
wrappedAdapter = null;
}
playingQueueAdapter = null;
layoutManager = null;
super.onDestroyView();
unbinder.unbind();
}
@Override
public void onPause() {
if (recyclerViewDragDropManager != null) {
recyclerViewDragDropManager.cancelDrag();
}
super.onPause();
}
@Override
public void onServiceConnected() {
updateQueue();
updateCurrentSong();
updateIsFavorite();
updateLyrics();
}
@Override
public void onPlayingMetaChanged() {
updateCurrentSong();
updateIsFavorite();
updateQueuePosition();
updateLyrics();
}
@Override
public void onQueueChanged() {
updateQueue();
}
@Override
public void onMediaStoreChanged() {
updateQueue();
}
private void updateQueue() {
playingQueueAdapter.swapDataSet(MusicPlayerRemote.getPlayingQueue(), MusicPlayerRemote.getPosition());
if (slidingUpPanelLayout.getPanelState() == SlidingUpPanelLayout.PanelState.COLLAPSED) {
resetToCurrentPosition();
}
}
private void updateQueuePosition() {
playingQueueAdapter.setCurrent(MusicPlayerRemote.getPosition());
if (slidingUpPanelLayout.getPanelState() == SlidingUpPanelLayout.PanelState.COLLAPSED) {
resetToCurrentPosition();
}
}
@SuppressWarnings("ConstantConditions")
private void updateCurrentSong() {
impl.updateCurrentSong(MusicPlayerRemote.getCurrentSong());
}
private void setUpSubFragments() {
playbackControlsFragment = (CardPlayerPlaybackControlsFragment) getChildFragmentManager().findFragmentById(R.id.playback_controls_fragment);
playerAlbumCoverFragment = (PlayerAlbumCoverFragment) getChildFragmentManager().findFragmentById(R.id.player_album_cover_fragment);
playerAlbumCoverFragment.setCallbacks(this);
}
private void setUpPlayerToolbar() {
toolbar.inflateMenu(R.menu.menu_player);
toolbar.setNavigationIcon(R.drawable.ic_close_white_24dp);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getActivity().onBackPressed();
}
});
toolbar.setOnMenuItemClickListener(this);
}
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_show_lyrics:
if (lyricsInfo != null)
LyricsDialog.create(lyricsInfo).show(getFragmentManager(), "LYRICS");
return true;
}
return super.onMenuItemClick(item);
}
private void setUpRecyclerView() {
recyclerViewDragDropManager = new RecyclerViewDragDropManager();
final GeneralItemAnimator animator = new RefactoredDefaultItemAnimator();
playingQueueAdapter = new PlayingQueueAdapter(
((AppCompatActivity) getActivity()),
MusicPlayerRemote.getPlayingQueue(),
MusicPlayerRemote.getPosition(),
R.layout.item_list,
false,
null);
wrappedAdapter = recyclerViewDragDropManager.createWrappedAdapter(playingQueueAdapter);
layoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(wrappedAdapter);
recyclerView.setItemAnimator(animator);
recyclerViewDragDropManager.attachRecyclerView(recyclerView);
layoutManager.scrollToPositionWithOffset(MusicPlayerRemote.getPosition() + 1, 0);
}
private void updateIsFavorite() {
if (updateIsFavoriteTask != null) updateIsFavoriteTask.cancel(false);
updateIsFavoriteTask = new AsyncTask<Song, Void, Boolean>() {
@Override
protected Boolean doInBackground(Song... params) {
Activity activity = getActivity();
if (activity != null) {
return MusicUtil.isFavorite(getActivity(), params[0]);
} else {
cancel(false);
return null;
}
}
@Override
protected void onPostExecute(Boolean isFavorite) {
Activity activity = getActivity();
if (activity != null) {
int res = isFavorite ? R.drawable.ic_favorite_white_24dp : R.drawable.ic_favorite_border_white_24dp;
int color = ToolbarContentTintHelper.toolbarContentColor(activity, Color.TRANSPARENT);
Drawable drawable = Util.getTintedVectorDrawable(activity, res, color);
toolbar.getMenu().findItem(R.id.action_toggle_favorite)
.setIcon(drawable)
.setTitle(isFavorite ? getString(R.string.action_remove_from_favorites) : getString(R.string.action_add_to_favorites));
}
}
}.execute(MusicPlayerRemote.getCurrentSong());
}
private void updateLyrics() {
if (updateLyricsAsyncTask != null) updateLyricsAsyncTask.cancel(false);
final Song song = MusicPlayerRemote.getCurrentSong();
updateLyricsAsyncTask = new AsyncTask<Void, Void, String>() {
@Override
protected void onPreExecute() {
super.onPreExecute();
lyricsInfo = null;
toolbar.getMenu().removeItem(R.id.action_show_lyrics);
}
@Override
protected String doInBackground(Void... params) {
try {
return AudioFileIO.read(new File(song.data)).getTagOrCreateDefault().getFirst(FieldKey.LYRICS);
} catch (Exception e) {
cancel(false);
return null;
}
}
@Override
protected void onPostExecute(String lyrics) {
super.onPostExecute(lyrics);
if (TextUtils.isEmpty(lyrics)) {
lyricsInfo = null;
if (toolbar != null) {
toolbar.getMenu().removeItem(R.id.action_show_lyrics);
}
} else {
lyricsInfo = new LyricsDialog.LyricInfo(song.title, lyrics);
Activity activity = getActivity();
if (toolbar != null && activity != null)
if (toolbar.getMenu().findItem(R.id.action_show_lyrics) == null) {
int color = ToolbarContentTintHelper.toolbarContentColor(activity, Color.TRANSPARENT);
Drawable drawable = Util.getTintedVectorDrawable(activity, R.drawable.ic_comment_text_outline_white_24dp, color);
toolbar.getMenu()
.add(Menu.NONE, R.id.action_show_lyrics, Menu.NONE, R.string.action_show_lyrics)
.setIcon(drawable)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
}
}
}
@Override
protected void onCancelled(String s) {
onPostExecute(null);
}
}.execute();
}
@Override
@ColorInt
public int getPaletteColor() {
return lastColor;
}
private void animateColorChange(final int newColor) {
impl.animateColorChange(newColor);
lastColor = newColor;
}
@Override
protected void toggleFavorite(Song song) {
super.toggleFavorite(song);
if (song.id == MusicPlayerRemote.getCurrentSong().id) {
if (MusicUtil.isFavorite(getActivity(), song)) {
playerAlbumCoverFragment.showHeartAnimation();
}
updateIsFavorite();
}
}
@Override
public void onShow() {
playbackControlsFragment.show();
}
@Override
public void onHide() {
playbackControlsFragment.hide();
onBackPressed();
}
@Override
public boolean onBackPressed() {
boolean wasExpanded = false;
if (slidingUpPanelLayout != null) {
wasExpanded = slidingUpPanelLayout.getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED;
slidingUpPanelLayout.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED);
}
return wasExpanded;
}
@Override
public void onColorChanged(int color) {
animateColorChange(color);
playbackControlsFragment.setDark(ColorUtil.isColorLight(color));
getCallbacks().onPaletteColorChanged();
}
@Override
public void onFavoriteToggled() {
toggleFavorite(MusicPlayerRemote.getCurrentSong());
}
@Override
public void onPanelSlide(View view, float slide) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
float density = getResources().getDisplayMetrics().density;
playingQueueCard.setCardElevation((6 * slide + 2) * density);
playbackControlsFragment.playPauseFab.setElevation((2 * Math.max(0, (1 - (slide * 16))) + 2) * density);
}
}
@Override
public void onPanelStateChanged(View panel, SlidingUpPanelLayout.PanelState previousState, SlidingUpPanelLayout.PanelState newState) {
switch (newState) {
case COLLAPSED:
onPanelCollapsed(panel);
break;
case ANCHORED:
slidingUpPanelLayout.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED); // this fixes a bug where the panel would get stuck for some reason
break;
}
}
public void onPanelCollapsed(View panel) {
resetToCurrentPosition();
}
private void resetToCurrentPosition() {
recyclerView.stopScroll();
layoutManager.scrollToPositionWithOffset(MusicPlayerRemote.getPosition() + 1, 0);
}
interface Impl {
void init();
void updateCurrentSong(Song song);
void animateColorChange(final int newColor);
void setUpPanelAndAlbumCoverHeight();
}
private static abstract class BaseImpl implements Impl {
protected CardPlayerFragment fragment;
public BaseImpl(CardPlayerFragment fragment) {
this.fragment = fragment;
}
public AnimatorSet createDefaultColorChangeAnimatorSet(int newColor) {
Animator backgroundAnimator;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
int topMargin = fragment.getResources().getDimensionPixelSize(R.dimen.status_bar_padding);
//noinspection ConstantConditions
int x = (int) (fragment.playbackControlsFragment.playPauseFab.getX() + fragment.playbackControlsFragment.playPauseFab.getWidth() / 2 + fragment.playbackControlsFragment.getView().getX());
int y = (int) (topMargin + fragment.playbackControlsFragment.playPauseFab.getY() + fragment.playbackControlsFragment.playPauseFab.getHeight() / 2 + fragment.playbackControlsFragment.getView().getY());
float startRadius = Math.max(fragment.playbackControlsFragment.playPauseFab.getWidth() / 2, fragment.playbackControlsFragment.playPauseFab.getHeight() / 2);
float endRadius = Math.max(fragment.colorBackground.getWidth(), fragment.colorBackground.getHeight());
fragment.colorBackground.setBackgroundColor(newColor);
backgroundAnimator = ViewAnimationUtils.createCircularReveal(fragment.colorBackground, x, y, startRadius, endRadius);
} else {
backgroundAnimator = ViewUtil.createBackgroundColorTransition(fragment.colorBackground, fragment.lastColor, newColor);
}
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(backgroundAnimator);
if (!ATHUtil.isWindowBackgroundDark(fragment.getActivity())) {
int adjustedLastColor = ColorUtil.isColorLight(fragment.lastColor) ? ColorUtil.darkenColor(fragment.lastColor) : fragment.lastColor;
int adjustedNewColor = ColorUtil.isColorLight(newColor) ? ColorUtil.darkenColor(newColor) : newColor;
Animator subHeaderAnimator = ViewUtil.createTextColorTransition(fragment.playerQueueSubHeader, adjustedLastColor, adjustedNewColor);
animatorSet.play(subHeaderAnimator);
}
animatorSet.setDuration(ViewUtil.PHONOGRAPH_ANIM_TIME);
return animatorSet;
}
@Override
public void animateColorChange(int newColor) {
if (ATHUtil.isWindowBackgroundDark(fragment.getActivity())) {
fragment.playerQueueSubHeader.setTextColor(ThemeStore.textColorSecondary(fragment.getActivity()));
}
}
}
@SuppressWarnings("ConstantConditions")
private static class PortraitImpl extends BaseImpl {
MediaEntryViewHolder currentSongViewHolder;
Song currentSong = Song.EMPTY_SONG;
public PortraitImpl(CardPlayerFragment fragment) {
super(fragment);
}
@Override
public void init() {
currentSongViewHolder = new MediaEntryViewHolder(fragment.getView().findViewById(R.id.current_song));
currentSongViewHolder.separator.setVisibility(View.VISIBLE);
currentSongViewHolder.shortSeparator.setVisibility(View.GONE);
currentSongViewHolder.image.setScaleType(ImageView.ScaleType.CENTER);
currentSongViewHolder.image.setColorFilter(ATHUtil.resolveColor(fragment.getActivity(), R.attr.iconColor, ThemeStore.textColorSecondary(fragment.getActivity())), PorterDuff.Mode.SRC_IN);
currentSongViewHolder.image.setImageResource(R.drawable.ic_volume_up_white_24dp);
currentSongViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// toggle the panel
if (fragment.slidingUpPanelLayout.getPanelState() == SlidingUpPanelLayout.PanelState.COLLAPSED) {
fragment.slidingUpPanelLayout.setPanelState(SlidingUpPanelLayout.PanelState.EXPANDED);
} else if (fragment.slidingUpPanelLayout.getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED) {
fragment.slidingUpPanelLayout.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED);
}
}
});
currentSongViewHolder.menu.setOnClickListener(new SongMenuHelper.OnClickSongMenu((AppCompatActivity) fragment.getActivity()) {
@Override
public Song getSong() {
return currentSong;
}
public int getMenuRes() {
return R.menu.menu_item_playing_queue_song;
}
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_remove_from_playing_queue:
MusicPlayerRemote.removeFromQueue(MusicPlayerRemote.getPosition());
return true;
case R.id.action_share:
SongShareDialog.create(getSong()).show(fragment.getFragmentManager(), "SONG_SHARE_DIALOG");
return true;
}
return super.onMenuItemClick(item);
}
});
}
@Override
public void setUpPanelAndAlbumCoverHeight() {
WidthFitSquareLayout albumCoverContainer = (WidthFitSquareLayout) fragment.getView().findViewById(R.id.album_cover_container);
int topMargin = fragment.getResources().getDimensionPixelSize(R.dimen.status_bar_padding);
final int availablePanelHeight = fragment.slidingUpPanelLayout.getHeight() - fragment.getView().findViewById(R.id.player_content).getHeight() + topMargin;
final int minPanelHeight = (int) ViewUtil.convertDpToPixel(72 + 24, fragment.getResources()) + topMargin;
if (availablePanelHeight < minPanelHeight) {
albumCoverContainer.getLayoutParams().height = albumCoverContainer.getHeight() - (minPanelHeight - availablePanelHeight);
albumCoverContainer.forceSquare(false);
}
fragment.slidingUpPanelLayout.setPanelHeight(Math.max(minPanelHeight, availablePanelHeight));
((AbsSlidingMusicPanelActivity) fragment.getActivity()).setAntiDragView(fragment.slidingUpPanelLayout.findViewById(R.id.player_panel));
}
@Override
public void updateCurrentSong(Song song) {
currentSong = song;
currentSongViewHolder.title.setText(song.title);
currentSongViewHolder.text.setText(song.artistName);
}
@Override
public void animateColorChange(int newColor) {
super.animateColorChange(newColor);
fragment.slidingUpPanelLayout.setBackgroundColor(fragment.lastColor);
createDefaultColorChangeAnimatorSet(newColor).start();
}
}
@SuppressWarnings("ConstantConditions")
private static class LandscapeImpl extends BaseImpl {
public LandscapeImpl(CardPlayerFragment fragment) {
super(fragment);
}
@Override
public void init() {
}
@Override
public void setUpPanelAndAlbumCoverHeight() {
int topMargin = fragment.getResources().getDimensionPixelSize(R.dimen.status_bar_padding);
int panelHeight = fragment.slidingUpPanelLayout.getHeight() - fragment.playbackControlsFragment.getView().getHeight() + topMargin;
fragment.slidingUpPanelLayout.setPanelHeight(panelHeight);
((AbsSlidingMusicPanelActivity) fragment.getActivity()).setAntiDragView(fragment.slidingUpPanelLayout.findViewById(R.id.player_panel));
}
@Override
public void updateCurrentSong(Song song) {
fragment.toolbar.setTitle(song.title);
fragment.toolbar.setSubtitle(song.artistName);
}
@Override
public void animateColorChange(int newColor) {
super.animateColorChange(newColor);
fragment.slidingUpPanelLayout.setBackgroundColor(fragment.lastColor);
AnimatorSet animatorSet = createDefaultColorChangeAnimatorSet(newColor);
animatorSet.play(ViewUtil.createBackgroundColorTransition(fragment.toolbar, fragment.lastColor, newColor))
.with(ViewUtil.createBackgroundColorTransition(fragment.getView().findViewById(R.id.status_bar), ColorUtil.darkenColor(fragment.lastColor), ColorUtil.darkenColor(newColor)));
animatorSet.start();
}
}
}