com.google.samples.apps.topeka.view.quiz.QuizActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.google.samples.apps.topeka.view.quiz.QuizActivity.java

Source

/*
 * Copyright 2015 Google Inc.
 *
 * 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.google.samples.apps.topeka.view.quiz;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorListenerAdapter;
import android.support.v4.view.animation.FastOutLinearInInterpolator;
import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.Window;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;

import com.google.samples.apps.topeka.R;
import com.google.samples.apps.topeka.helper.ApiLevelHelper;
import com.google.samples.apps.topeka.helper.ViewUtils;
import com.google.samples.apps.topeka.model.Avatar;
import com.google.samples.apps.topeka.model.AvatarEntity;
import com.google.samples.apps.topeka.model.Category;
import com.google.samples.apps.topeka.model.CategoryEntity;
import com.google.samples.apps.topeka.model.JsonAttributes;
import com.google.samples.apps.topeka.model.Theme;
import com.google.samples.apps.topeka.model.ThemeEntity;
import com.google.samples.apps.topeka.model.mapper.CategoryDataMapper;
import com.google.samples.apps.topeka.model.quiz.Quiz;
import com.google.samples.apps.topeka.persistence.CategoryRepositoryImpl;
import com.google.samples.apps.topeka.persistence.PlayerRepositoryImpl;
import com.google.samples.apps.topeka.presenter.quiz.QuizContract;
import com.google.samples.apps.topeka.presenter.quiz.QuizPresenter;
import com.google.samples.apps.topeka.repository.CategoryRepository;
import com.google.samples.apps.topeka.repository.PlayerRepository;
import com.google.samples.apps.topeka.widget.TextSharedElementCallback;

import java.util.List;

import static com.google.samples.apps.topeka.adapter.CategoryAdapter.DRAWABLE;

public class QuizActivity extends AppCompatActivity
        implements QuizContract.View, QuizFragment.FragmentViewCreatedListener {

    private static final String TAG = "QuizActivity";
    private static final String IMAGE_CATEGORY = "image_category_";
    private static final String STATE_IS_PLAYING = "isPlaying";
    private static final String FRAGMENT_TAG = "Quiz";

    private Interpolator mInterpolator;
    private FloatingActionButton mQuizFab;
    private ImageView mIcon;
    private Animator mCircularReveal;
    private ObjectAnimator mColorChange;
    private View mToolbarBack;

    private boolean mSavedStateIsPlaying;

    private QuizContract.Presenter presenter;

    private QuizFragment mQuizFragment;

    private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
        @Override
        public void onClick(final View v) {
            switch (v.getId()) {
            case R.id.fab_quiz:
                presenter.actionStartQuizClicked();
                break;
            case R.id.quiz_done:
                ActivityCompat.finishAfterTransition(QuizActivity.this);
                break;
            case R.id.back:
                onBackPressed();
                break;
            default:
                throw new UnsupportedOperationException(
                        "OnClick has not been implemented for " + getResources().getResourceName(v.getId()));
            }
        }
    };

    public static Intent getStartIntent(Context context, Category category) {
        Intent starter = new Intent(context, QuizActivity.class);
        starter.putExtra(Category.TAG, category.getId());
        return starter;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        mInterpolator = new FastOutSlowInInterpolator();

        super.onCreate(savedInstanceState);

        int categoryNameTextSize = getResources().getDimensionPixelSize(R.dimen.category_item_text_size);
        int paddingStart = getResources().getDimensionPixelSize(R.dimen.spacing_double);
        final int startDelay = getResources().getInteger(R.integer.toolbar_transition_duration);
        ActivityCompat.setEnterSharedElementCallback(this,
                new TextSharedElementCallback(categoryNameTextSize, paddingStart) {
                    @Override
                    public void onSharedElementStart(List<String> sharedElementNames, List<View> sharedElements,
                            List<View> sharedElementSnapshots) {
                        super.onSharedElementStart(sharedElementNames, sharedElements, sharedElementSnapshots);
                        mToolbarBack.setScaleX(0f);
                        mToolbarBack.setScaleY(0f);
                    }

                    @Override
                    public void onSharedElementEnd(List<String> sharedElementNames, List<View> sharedElements,
                            List<View> sharedElementSnapshots) {
                        super.onSharedElementEnd(sharedElementNames, sharedElements, sharedElementSnapshots);
                        // Make sure to perform this animation after the transition has ended.
                        ViewCompat.animate(mToolbarBack).setStartDelay(startDelay).scaleX(1f).scaleY(1f).alpha(1f);
                    }
                });

        if (null != savedInstanceState) {
            mSavedStateIsPlaying = savedInstanceState.getBoolean(STATE_IS_PLAYING);
        }

        this.presenter = initPresenter();

    }

    private QuizContract.Presenter initPresenter() {
        String categoryId = getIntent().getStringExtra(Category.TAG);
        CategoryRepository categoryRepository = new CategoryRepositoryImpl(getApplication());
        PlayerRepository playerRepository = new PlayerRepositoryImpl(getApplication());

        return new QuizPresenter(categoryId, categoryRepository, playerRepository);
    }

    @Override
    protected void onResume() {
        this.presenter.setView(this);
        this.presenter.resume();

        if (mSavedStateIsPlaying) {
            this.presenter.actionQuizPlayShoudBeRestored();
        }

        super.onResume();
    }

    @Override
    protected void onPause() {
        this.presenter.pause();
        super.onPause();
    }

    private void initQuizFragment(Theme theme) {
        if (mQuizFragment != null) {
            return;
        }
        mQuizFragment = QuizFragment.newInstance(theme, this);
        // the toolbar should not have more elevation than the content while playing
        setToolbarElevation(false);
    }

    @Override
    public void onSaveInstanceState(@NonNull Bundle outState) {
        outState.putBoolean(STATE_IS_PLAYING, mQuizFab.getVisibility() == View.GONE);
        super.onSaveInstanceState(outState);
    }

    @Override
    public void onBackPressed() {
        if (mIcon == null || mQuizFab == null) {
            // Skip the animation if icon or fab are not initialized.
            super.onBackPressed();
            return;
        }

        ViewCompat.animate(mToolbarBack).scaleX(0f).scaleY(0f).alpha(0f).setDuration(100).start();

        // Scale the icon and fab to 0 size before calling onBackPressed if it exists.
        ViewCompat.animate(mIcon).scaleX(.7f).scaleY(.7f).alpha(0f).setInterpolator(mInterpolator).start();

        ViewCompat.animate(mQuizFab).scaleX(0f).scaleY(0f).setInterpolator(mInterpolator).setStartDelay(100)
                .setListener(new ViewPropertyAnimatorListenerAdapter() {
                    @SuppressLint("NewApi")
                    @Override
                    public void onAnimationEnd(View view) {
                        if (isFinishing() || (ApiLevelHelper.isAtLeast(Build.VERSION_CODES.JELLY_BEAN_MR1)
                                && isDestroyed())) {
                            return;
                        }
                        QuizActivity.super.onBackPressed();
                    }
                }).start();
    }

    private void revealFragmentContainer(final View clickedView, final FrameLayout fragmentContainer,
            int themeAccentColor) {
        if (ApiLevelHelper.isAtLeast(Build.VERSION_CODES.LOLLIPOP)) {
            revealFragmentContainerLollipop(clickedView, fragmentContainer, themeAccentColor);
        } else {
            fragmentContainer.setVisibility(View.VISIBLE);
            clickedView.setVisibility(View.GONE);
            mIcon.setVisibility(View.GONE);
        }
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private void revealFragmentContainerLollipop(final View clickedView, final FrameLayout fragmentContainer,
            int themeAccentColor) {
        prepareCircularReveal(clickedView, fragmentContainer, themeAccentColor);

        ViewCompat.animate(clickedView).scaleX(0).scaleY(0).alpha(0).setInterpolator(mInterpolator)
                .setListener(new ViewPropertyAnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(View view) {
                        fragmentContainer.setVisibility(View.VISIBLE);
                        clickedView.setVisibility(View.GONE);
                    }
                }).start();

        fragmentContainer.setVisibility(View.VISIBLE);
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.play(mCircularReveal).with(mColorChange);
        animatorSet.start();
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private void prepareCircularReveal(View startView, FrameLayout targetView, int themeAccentColor) {
        int centerX = (startView.getLeft() + startView.getRight()) / 2;
        // Subtract the start view's height to adjust for relative coordinates on screen.
        int centerY = (startView.getTop() + startView.getBottom()) / 2 - startView.getHeight();
        float endRadius = (float) Math.hypot(centerX, centerY);
        mCircularReveal = ViewAnimationUtils.createCircularReveal(targetView, centerX, centerY,
                startView.getWidth(), endRadius);
        mCircularReveal.setInterpolator(new FastOutLinearInInterpolator());

        mCircularReveal.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                mIcon.setVisibility(View.GONE);
                mCircularReveal.removeListener(this);
            }
        });
        // Adding a color animation from the FAB's color to transparent creates a dissolve like
        // effect to the circular reveal.
        mColorChange = ObjectAnimator.ofInt(targetView, ViewUtils.FOREGROUND_COLOR, themeAccentColor,
                Color.TRANSPARENT);
        mColorChange.setEvaluator(new ArgbEvaluator());
        mColorChange.setInterpolator(mInterpolator);
    }

    @SuppressLint("NewApi")
    public void setToolbarElevation(boolean shouldElevate) {
        if (ApiLevelHelper.isAtLeast(Build.VERSION_CODES.LOLLIPOP)) {
            mToolbarBack.setElevation(shouldElevate ? getResources().getDimension(R.dimen.elevation_header) : 0);
        }
    }

    @Override
    public void displayDoneFab() {
        /* We're re-using the already existing fab and give it some
         * new values. This has to run delayed due to the queued animation
         * to hide the fab initially.
         */
        if (null != mCircularReveal && mCircularReveal.isRunning()) {
            mCircularReveal.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    showQuizFabWithDoneIcon();
                    mCircularReveal.removeListener(this);
                }
            });
        } else {
            showQuizFabWithDoneIcon();
        }
    }

    private void showQuizFabWithDoneIcon() {
        mQuizFab.setImageResource(R.drawable.ic_tick);
        mQuizFab.setId(R.id.quiz_done);
        mQuizFab.setVisibility(View.VISIBLE);
        mQuizFab.setScaleX(0f);
        mQuizFab.setScaleY(0f);
        ViewCompat.animate(mQuizFab).scaleX(1).scaleY(1).setInterpolator(mInterpolator).setListener(null).start();
    }

    @Override
    public void setResultSolved(String categoryId) {
        Intent categoryIntent = new Intent();
        categoryIntent.putExtra(JsonAttributes.ID, categoryId);
        setResult(R.id.solved, categoryIntent);
    }

    @Override
    public void initLayout(ThemeEntity theme) {
        final Theme androidTheme = Theme.values()[theme.ordinal()];
        setTheme(androidTheme.getStyleId());
        setContentView(R.layout.activity_quiz);

        setTheme(androidTheme.getStyleId());
        if (ApiLevelHelper.isAtLeast(Build.VERSION_CODES.LOLLIPOP)) {
            Window window = getWindow();
            window.setStatusBarColor(ContextCompat.getColor(this, androidTheme.getPrimaryDarkColor()));
        }
    }

    @Override
    public void renderQuizCategoryIcon(String categoryId) {
        mIcon = (ImageView) findViewById(R.id.icon);
        int resId = getResources().getIdentifier(IMAGE_CATEGORY + categoryId, DRAWABLE,
                getApplicationContext().getPackageName());
        mIcon.setImageResource(resId);
        mIcon.setImageResource(resId);
        ViewCompat.animate(mIcon).scaleX(1).scaleY(1).alpha(1).setInterpolator(mInterpolator).setStartDelay(300)
                .start();
    }

    @Override
    public void refreshQuizCategoryPlayFab(boolean visible) {
        mQuizFab = (FloatingActionButton) findViewById(R.id.fab_quiz);
        mQuizFab.setImageResource(R.drawable.ic_play);
        if (mSavedStateIsPlaying) {
            mQuizFab.hide();
        } else {
            mQuizFab.show();
        }
        mQuizFab.setOnClickListener(mOnClickListener);
    }

    @Override
    public void initToolbar(String categoryName, ThemeEntity theme) {
        mToolbarBack = findViewById(R.id.back);
        mToolbarBack.setOnClickListener(mOnClickListener);

        final Theme androidTheme = Theme.values()[theme.ordinal()];

        TextView titleView = (TextView) findViewById(R.id.category_title);
        titleView.setText(categoryName);
        titleView.setTextColor(ContextCompat.getColor(this, androidTheme.getTextPrimaryColor()));

        if (mSavedStateIsPlaying) {
            // the toolbar should not have more elevation than the content while playing
            setToolbarElevation(false);
        }
    }

    @Override
    public void renderQuizView(ThemeEntity theme, boolean withFabRevealAnimation) {
        final Theme androidTheme = Theme.values()[theme.ordinal()];

        initQuizFragment(androidTheme);

        getSupportFragmentManager().beginTransaction()
                .replace(R.id.quiz_fragment_container, mQuizFragment, FRAGMENT_TAG).commit();

        final FrameLayout container = (FrameLayout) findViewById(R.id.quiz_fragment_container);
        container.setBackgroundColor(ContextCompat.getColor(this, androidTheme.getWindowBackgroundColor()));
        revealFragmentContainer(findViewById(R.id.fab_quiz), container, androidTheme.getAccentColor());
        // the toolbar should not have more elevation than the content while playing
        setToolbarElevation(false);
    }

    @Override
    public void displayProgress(int currentQuizPosition, int quizPositionsCount) {
        mQuizFragment.displayProgress(currentQuizPosition, quizPositionsCount);
    }

    @Override
    public void displayAvatar(AvatarEntity avatar) {
        mQuizFragment.displayAvatar(Avatar.values()[avatar.ordinal()]);
    }

    @Override
    public void loadQuizDetails(CategoryEntity category, int quizPosition) {
        mQuizFragment.loadQuizView(CategoryDataMapper.from(category), quizPosition);
    }

    @Override
    public void showNextQuizPage() {
        mQuizFragment.showNextQuizPage();
    }

    @Override
    public void displaySummary(CategoryEntity category) {
        mQuizFragment.showSummary(CategoryDataMapper.from(category));
        setToolbarElevation(false);
        displayDoneFab();
    }

    // QuizFragment.FragmentViewCreatedListener
    @Override
    public void onQuizViewRendered() {
        this.presenter.quizViewRendered();
    }

    // todo
    // called from AbsQuizView instance that casts its context to QuizActivity
    // should be done more elegant. passing listener in quiz adapter doesn't work because
    // AbsQuizView instance is recreated somewhere and loses listener instance.
    public <Q extends Quiz> void notifyAnswerSubmitted(Q mQuiz, boolean answerCorrect) {
        this.presenter.actionQuizAnswerSubmitted(mQuiz, answerCorrect);
    }
}