com.doctoror.fuckoffmusicplayer.presentation.queue.QueueActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.doctoror.fuckoffmusicplayer.presentation.queue.QueueActivity.java

Source

/*
 * Copyright (C) 2016 Yaroslav Mytkalyk
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.doctoror.fuckoffmusicplayer.presentation.queue;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.res.Configuration;
import android.databinding.DataBindingUtil;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.support.annotation.WorkerThread;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.ViewCompat;
import android.support.v7.app.ActionBar;
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.support.v7.widget.helper.ItemTouchHelper;
import android.text.TextUtils;
import android.transition.Transition;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.ImageView;
import android.widget.Toast;

import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestBuilder;
import com.bumptech.glide.RequestManager;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.Target;
import com.doctoror.fuckoffmusicplayer.R;
import com.doctoror.fuckoffmusicplayer.data.util.CollectionUtils;
import com.doctoror.fuckoffmusicplayer.databinding.ActivityQueueBinding;
import com.doctoror.fuckoffmusicplayer.domain.media.CurrentMediaProvider;
import com.doctoror.fuckoffmusicplayer.domain.playback.PlaybackData;
import com.doctoror.fuckoffmusicplayer.domain.playback.PlaybackState;
import com.doctoror.fuckoffmusicplayer.domain.playback.initializer.PlaybackInitializer;
import com.doctoror.fuckoffmusicplayer.domain.queue.Media;
import com.doctoror.fuckoffmusicplayer.presentation.base.BaseActivity;
import com.doctoror.fuckoffmusicplayer.presentation.nowplaying.NowPlayingActivity;
import com.doctoror.fuckoffmusicplayer.presentation.transition.CardVerticalGateTransition;
import com.doctoror.fuckoffmusicplayer.presentation.transition.SlideFromBottomHelper;
import com.doctoror.fuckoffmusicplayer.presentation.transition.TransitionListenerAdapter;
import com.doctoror.fuckoffmusicplayer.presentation.transition.TransitionUtils;
import com.doctoror.fuckoffmusicplayer.presentation.transition.VerticalGateTransition;
import com.doctoror.fuckoffmusicplayer.presentation.util.CoordinatorLayoutUtil;
import com.doctoror.fuckoffmusicplayer.presentation.util.ViewUtils;
import com.doctoror.fuckoffmusicplayer.presentation.widget.DisableableAppBarLayout;
import com.doctoror.fuckoffmusicplayer.presentation.widget.ItemTouchHelperViewHolder;
import com.f2prateek.dart.Dart;
import com.f2prateek.dart.InjectExtra;

import org.parceler.Parcel;
import org.parceler.Parcels;

import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import javax.inject.Inject;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import dagger.android.AndroidInjection;

/**
 * "Playlist" activity
 */
public final class QueueActivity extends BaseActivity implements DeleteMediaDialogFragment.Callback {

    public static final String TRANSITION_NAME_ALBUM_ART = "PlaylistActivity.TRANSITION_NAME_ALBUM_ART";

    public static final String TRANSITION_NAME_ROOT = "PlaylistActivity.TRANSITION_NAME_ROOT";

    private static final String EXTRA_STATE = "EXTRA_STATE";

    private final RequestOptions requestOptions = new RequestOptions().diskCacheStrategy(DiskCacheStrategy.NONE);

    private final RequestOptions dontAnimateOptions = new RequestOptions().diskCacheStrategy(DiskCacheStrategy.NONE)
            .dontAnimate();

    private final QueueActivityModel mModel = new QueueActivityModel();

    private RequestManager mRequestManager;

    private QueueRecyclerAdapter mAdapter;
    private CoordinatorLayoutUtil.AnchorParams mFabAnchorParams;

    private int mShortAnimTime;
    private int mMediumAnimTime;

    @InjectExtra
    List<Media> queue;

    @InjectExtra
    boolean isNowPlayingQueue;

    @Nullable
    @InjectExtra
    String title;

    @InjectExtra
    boolean hasCoverTransition;

    @InjectExtra
    boolean hasItemViewTransition;

    @BindView(R.id.root)
    View root;

    @BindView(R.id.appBar)
    DisableableAppBarLayout appBar;

    @BindView(R.id.toolbar)
    Toolbar toolbar;

    @BindView(R.id.albumArt)
    ImageView albumArt;

    @BindView(R.id.albumArtDim)
    View albumArtDim;

    @BindView(R.id.fab)
    View fab;

    @Nullable
    @BindView(R.id.cardHostScrollView)
    View cardHostScrollView;

    @Nullable
    @BindView(R.id.cardView)
    CardView cardView;

    @BindView(R.id.recyclerView)
    RecyclerView recyclerView;

    private String mCoverUri;
    private int mAppbarOffset;

    private boolean mCreatedWithInstanceState;
    private Toast mToastRemovedFromQueue;

    @Inject
    CurrentMediaProvider currentMediaProvider;

    @Inject
    PlaybackData mPlaybackData;

    @Inject
    PlaybackInitializer mPlaybackInitializer;

    @Override
    protected void onCreate(@Nullable final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Dart.inject(this);
        AndroidInjection.inject(this);
        mRequestManager = Glide.with(this);

        mShortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime);
        mMediumAnimTime = getResources().getInteger(android.R.integer.config_mediumAnimTime);

        if (!TextUtils.isEmpty(title)) {
            setTitle(title);
        }

        mAdapter = new QueueRecyclerAdapter(this, queue);
        mAdapter.setTrackListener(new TrackListenerImpl());
        mAdapter.registerAdapterDataObserver(mAdapterDataObserver);
        mModel.setRecyclerAdpter(mAdapter);

        final ActivityQueueBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_queue);
        binding.setModel(mModel);

        ButterKnife.bind(this);

        appBar.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> mAppbarOffset = verticalOffset);

        initAlbumArtAndToolbar(binding);
        initRecyclerView();

        if (TransitionUtils.supportsActivityTransitions()) {
            QueueActivityLollipop.applyTransitions(this, cardView != null);
        }

        mCreatedWithInstanceState = savedInstanceState != null;
    }

    @Override
    protected void onRestoreInstanceState(final Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        if (savedInstanceState != null) {
            final State state = Parcels.unwrap(savedInstanceState.getParcelable(EXTRA_STATE));
            mFabAnchorParams = state.fabAnchorParams;
            queue = state.queue;
            mAdapter.setItems(queue);

            fab.setScaleX(1f);
            fab.setScaleY(1f);
        }
    }

    @Override
    protected void onPostCreate(@Nullable final Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        if (TransitionUtils.supportsActivityTransitions()) {
            QueueActivityLollipop.addEnterTransitionListener(this);
        }
    }

    private void setAppBarCollapsibleIfNeeded() {
        ViewUtils.setAppBarCollapsibleIfScrollableViewIsLargeEnoughToScroll(root, appBar, recyclerView,
                ViewUtils.getOverlayTop(cardHostScrollView != null ? cardHostScrollView : recyclerView));
    }

    private void initRecyclerView() {
        recyclerView.setLayoutManager(new LinearLayoutManager(this) {
            @Override
            public void onLayoutChildren(final RecyclerView.Recycler recycler, final RecyclerView.State state) {
                super.onLayoutChildren(recycler, state);
                setAppBarCollapsibleIfNeeded();
            }
        });
        final QueueRecyclerAdapter adapter = (QueueRecyclerAdapter) mModel.getRecyclerAdapter().get();
        if (adapter == null) {
            throw new IllegalStateException("Adapter in Model must be set");
        }
        final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new ItemTouchHelperImpl(adapter));
        itemTouchHelper.attachToRecyclerView(recyclerView);
    }

    private void initAlbumArtAndToolbar(@NonNull final ActivityQueueBinding binding) {
        setSupportActionBar(toolbar);
        ViewCompat.setTransitionName(binding.getRoot(), QueueActivity.TRANSITION_NAME_ROOT);
        ViewCompat.setTransitionName(albumArt, QueueActivity.TRANSITION_NAME_ALBUM_ART);

        String pic = null;
        for (final Media media : queue) {
            pic = media.getAlbumArt();
            if (pic != null) {
                break;
            }
        }
        mCoverUri = pic;

        if (TextUtils.isEmpty(pic)) {
            mRequestManager.clear(albumArt);
            showPlaceholderArt();
            onImageSet();
        } else {
            loadAlbumArt(pic);
        }
    }

    @SuppressLint("CheckResult")
    private void loadAlbumArt(@NonNull final String uri) {
        final RequestBuilder<Drawable> b = mRequestManager.asDrawable().load(uri);

        if (hasCoverTransition || hasItemViewTransition) {
            supportPostponeEnterTransition();
            b.apply(dontAnimateOptions);
        } else {
            b.apply(requestOptions);
        }
        b.listener(new AlbumArtRequestListener()).into(albumArt);
    }

    private void showPlaceholderArt() {
        albumArt.setImageResource(R.drawable.album_art_placeholder);
        albumArt.setAlpha(1f);
    }

    private void onImageSet() {
        if (hasCoverTransition || hasItemViewTransition) {
            supportStartPostponedEnterTransition();
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (mCreatedWithInstanceState || !TransitionUtils.supportsActivityTransitions()
                || (!hasItemViewTransition && !hasCoverTransition)) {
            onEnterTransitionFinished();
        }
        if (mFabAnchorParams != null) {
            CoordinatorLayoutUtil.applyAnchorParams(fab, mFabAnchorParams);
            fab.post(() -> fab.requestLayout());
            mFabAnchorParams = null;
        }
        if (isNowPlayingQueue) {
            disposeOnStop(mPlaybackData.playbackStateObservable().subscribe(this::onPlaybackStateChanged));
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        fab.setScaleX(1f);
        fab.setScaleY(1f);
        albumArtDim.setAlpha(1f);
        albumArt.clearColorFilter();
    }

    @Override
    protected void onSaveInstanceState(final Bundle outState) {
        super.onSaveInstanceState(outState);
        final State state = new State();
        state.queue = queue;
        state.fabAnchorParams = mFabAnchorParams;
        outState.putParcelable(EXTRA_STATE, Parcels.wrap(state));
    }

    @Override
    public void setSupportActionBar(@Nullable final Toolbar toolbar) {
        super.setSupportActionBar(toolbar);
        final ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            actionBar.setDisplayOptions(
                    ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_HOME_AS_UP);
        }
    }

    @Override
    public void onPerformDelete(final long id) {
        if (!isFinishingAfterTransition()) {
            mAdapter.removeItemWithId(id);

            final Iterator<Media> i = queue.iterator();
            while (i.hasNext()) {
                if (i.next().getId() == id) {
                    i.remove();
                    break;
                }
            }
        }
    }

    private void onQueueEmpty() {
        ActivityCompat.finishAfterTransition(this);
    }

    /**
     * This is used to avoid
     * <pre>
     *   android.content.res.Resources$NotFoundException: Unable to find resource ID #0x0
     *     at android.content.res.ResourcesImpl.getResourceName(ResourcesImpl.java:253)
     *     at android.content.res.Resources.getResourceName(Resources.java:1933)
     *     at android.support.design.widget.CoordinatorLayout$LayoutParams.resolveAnchorView(CoordinatorLayout.java:3072)
     * </pre>
     */
    private void prepareFabForExitTransition() {
        final ViewGroup.LayoutParams params = fab.getLayoutParams();
        if (params instanceof CoordinatorLayout.LayoutParams) {
            ((CoordinatorLayout.LayoutParams) params).setAnchorId(View.NO_ID);
            fab.setLayoutParams(params);
        }
    }

    @OnClick(R.id.fab)
    public void onFabClick(@NonNull final View view) {
        onPlayClick(view, 0);
    }

    @WorkerThread
    private void onPlaybackStateChanged(@NonNull final PlaybackState state) {
        final Media media = state == PlaybackState.STATE_PLAYING ? currentMediaProvider.getCurrentMedia() : null;
        //noinspection WrongThread
        runOnUiThread(() -> onNowPlayingMediaChanged(media));
    }

    @UiThread
    private void onNowPlayingMediaChanged(@Nullable final Media media) {
        mAdapter.setNowPlayingId(media != null ? media.getId() : 0);
    }

    private void onPlayClick(@NonNull final View clickedView, final int queuePosition) {

        mPlaybackInitializer.setQueueAndPlay(queue, queuePosition);
        final Media media = CollectionUtils.getItemSafe(queue, queuePosition);
        final boolean shouldPassCoverView = mAppbarOffset == 0
                && TextUtils.equals(mCoverUri, media != null ? media.getAlbumArt() : null);
        if (shouldPassCoverView) {
            prepareViewsAndExit(() -> startNowPlayingActivity(albumArt, null));
        } else {
            mFabAnchorParams = CoordinatorLayoutUtil.getAnchorParams(fab);
            CoordinatorLayoutUtil.clearAnchorGravityAndApplyMargins(fab);
            startNowPlayingActivity(null, clickedView);
        }
    }

    private void startNowPlayingActivity(@Nullable final View albumArt, @Nullable final View listItemView) {
        if (isNowPlayingQueue) {
            // Note that starting a transition from here when returning to already running
            // NowPlayingActivity causes memory leak in ExitTransitionCoordinator. Thus null views
            // are passed here to avoid this.
            // https://code.google.com/p/android/issues/detail?id=170469
            prepareFabForExitTransition();
            NowPlayingActivity.start(this, null, null);
        } else {
            NowPlayingActivity.start(this, albumArt, listItemView);
        }
    }

    private void onEnterTransitionFinished() {
        if (fab.getScaleX() != 1f) {
            fab.animate().scaleX(1f).scaleY(1f).setDuration(mShortAnimTime).start();
        }
        if (albumArtDim.getAlpha() != 1f) {
            albumArtDim.animate().alpha(1f).setDuration(mShortAnimTime).start();
        }
        if (cardView != null && cardView.getVisibility() != View.VISIBLE) {
            if (TransitionUtils.supportsActivityTransitions() && hasCoverTransition) {
                cardView.setTranslationY(SlideFromBottomHelper.getStartTranslation(cardView));
                cardView.setVisibility(View.VISIBLE);
                SlideFromBottomHelper.createAnimator(cardView).setDuration(mMediumAnimTime).start();
            } else {
                cardView.setVisibility(View.VISIBLE);
            }
        }
    }

    private void prepareViewsAndExit(@NonNull final Runnable exitAction) {
        if (!TransitionUtils.supportsActivityTransitions()
                || (fab.getScaleX() == 0f && albumArtDim.getAlpha() == 0f)) {
            exitAction.run();
        } else {
            final boolean isLandscape = getResources()
                    .getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
            // Landscape Now Playing has a dim, so dim the ImageView and send it
            if (isLandscape) {
                albumArtDim.setAlpha(0f);
                albumArt.setColorFilter(ContextCompat.getColor(this, R.color.translucentBackground),
                        PorterDuff.Mode.SRC_ATOP);
            } else {
                // Portrait NowPlaying does not have a dim. Fade out the dim before animating.
                albumArtDim.animate().alpha(0f).setDuration(mShortAnimTime).start();
            }
            fab.animate().scaleX(0f).scaleY(0f).setDuration(mShortAnimTime)
                    .setListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(final Animator animation) {
                            exitAction.run();
                        }
                    }).start();
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mAdapter.unregisterAdapterDataObserver(mAdapterDataObserver);
    }

    private void showToastRemovedFromQueue(@NonNull final Media media) {
        if (mToastRemovedFromQueue != null && mToastRemovedFromQueue.getView().getWindowToken() != null) {
            mToastRemovedFromQueue.cancel();
        }
        mToastRemovedFromQueue = Toast.makeText(this, getString(R.string.s_removed_from_queue, media.getTitle()),
                Toast.LENGTH_SHORT);
        mToastRemovedFromQueue.show();
    }

    @Parcel
    static final class State {

        List<Media> queue;
        CoordinatorLayoutUtil.AnchorParams fabAnchorParams;
    }

    private final class AlbumArtRequestListener implements RequestListener<Drawable> {

        @Override
        public boolean onLoadFailed(@Nullable final GlideException e, @NonNull final Object model,
                @NonNull final Target<Drawable> target, final boolean isFirstResource) {
            mCoverUri = null;
            showPlaceholderArt();
            onImageSet();
            return true;
        }

        @Override
        public boolean onResourceReady(@NonNull final Drawable resource, @NonNull final Object model,
                @NonNull final Target<Drawable> target, @NonNull final DataSource dataSource,
                final boolean isFirstResource) {
            onImageSet();
            return false;
        }
    }

    private final class TrackListenerImpl implements QueueRecyclerAdapter.TrackListener {

        @Override
        public void onTrackClick(@NonNull final View itemView, final int position) {
            onPlayClick(itemView, position);
        }

        @Override
        public void onTrackDeleteClick(@NonNull final Media item) {
            DeleteMediaDialogFragment.show(QueueActivity.this, getSupportFragmentManager(), item.getId(),
                    item.getTitle());
        }

        @Override
        public void onTracksSwapped(final int i, final int j) {
            if (i < queue.size() && j < queue.size()) {
                Collections.swap(queue, i, j);
            }
            if (isNowPlayingQueue) {
                mPlaybackData.setPlayQueue(queue);
            }
        }
    }

    private final class ItemTouchHelperImpl extends ItemTouchHelper.SimpleCallback {

        @NonNull
        private final QueueRecyclerAdapter mAdapter;

        ItemTouchHelperImpl(@NonNull final QueueRecyclerAdapter adapter) {
            super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT);
            mAdapter = adapter;
        }

        @Override
        public boolean isLongPressDragEnabled() {
            return true;
        }

        @Override
        public boolean isItemViewSwipeEnabled() {
            return true;
        }

        @Override
        public boolean onMove(final RecyclerView recyclerView, final RecyclerView.ViewHolder source,
                final RecyclerView.ViewHolder target) {
            //noinspection SimplifiableIfStatement
            if (source.getItemViewType() != target.getItemViewType()) {
                return false;
            }

            mAdapter.onItemMove(source.getAdapterPosition(), target.getAdapterPosition());
            return true;
        }

        @Override
        public int getMovementFlags(final RecyclerView recyclerView, final RecyclerView.ViewHolder viewHolder) {
            final int swipeFlags = ItemTouchHelper.LEFT;
            int dragFlags = 0;
            if (mAdapter.getItemCount() > 1) {
                dragFlags |= ItemTouchHelper.UP | ItemTouchHelper.DOWN;
            }
            return makeMovementFlags(dragFlags, swipeFlags);
        }

        @Override
        public void onSwiped(final RecyclerView.ViewHolder viewHolder, final int swipeDir) {
            final int pos = viewHolder.getAdapterPosition();
            final Media media = mAdapter.getItem(pos);
            queue.remove(pos);
            if (isNowPlayingQueue) {
                mPlaybackData.setPlayQueue(queue);
            }
            mAdapter.removeItem(pos);
            showToastRemovedFromQueue(media);
        }

        @Override
        public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
            if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
                if (viewHolder instanceof ItemTouchHelperViewHolder) {
                    ((ItemTouchHelperViewHolder) viewHolder).onItemSelected();
                }
            }

            super.onSelectedChanged(viewHolder, actionState);
        }

        @Override
        public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
            super.clearView(recyclerView, viewHolder);
            if (viewHolder instanceof ItemTouchHelperViewHolder) {
                ((ItemTouchHelperViewHolder) viewHolder).onItemClear();
            }
        }
    }

    private final RecyclerView.AdapterDataObserver mAdapterDataObserver = new RecyclerView.AdapterDataObserver() {

        @Override
        public void onChanged() {
            checkIfEmpty();
        }

        @Override
        public void onItemRangeRemoved(final int positionStart, final int itemCount) {
            checkIfEmpty();
        }

        private void checkIfEmpty() {
            if (mAdapter.getItemCount() == 0 && !isFinishing()) {
                onQueueEmpty();
            }
        }
    };

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private static final class QueueActivityLollipop {

        static void applyTransitions(@NonNull final BaseActivity activity, final boolean hasCardView) {
            TransitionUtils.clearSharedElementsOnReturn(activity);
            final Window window = activity.getWindow();
            window.setReturnTransition(
                    hasCardView ? new CardVerticalGateTransition() : new VerticalGateTransition());
        }

        static void addEnterTransitionListener(@NonNull final QueueActivity activity) {
            final Transition enter = activity.getWindow().getSharedElementEnterTransition();
            if (enter != null) {
                enter.addListener(new TransitionListenerAdapter() {
                    @Override
                    public void onTransitionEnd(final Transition transition) {
                        activity.onEnterTransitionFinished();
                    }
                });
            }
        }
    }
}