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(); } } }