website.openeng.anki.StudyOptionsFragment.java Source code

Java tutorial

Introduction

Here is the source code for website.openeng.anki.StudyOptionsFragment.java

Source

/****************************************************************************************
 * This program is free software; you can redistribute it and/or modify it under        *
 * the terms of the GNU General Public License as published by the Free Software        *
 * Foundation; either version 3 of the License, or (at your option) any later           *
 * version.                                                                             *
 *                                                                                      *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
 * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
 *                                                                                      *
 * You should have received a copy of the GNU General Public License along with         *
 * this program. If not, see <http://www.gnu.org/licenses/>.                            *
 ****************************************************************************************/

package website.openeng.anki;

import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v7.app.AppCompatActivity;
import android.text.Html;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TableLayout;
import android.widget.TextView;
import android.widget.Toast;

import com.afollestad.materialdialogs.MaterialDialog;
import website.openeng.anim.ActivityTransitionAnimation;
import website.openeng.anim.ViewAnimation;
import website.openeng.anki.dialogs.CustomStudyDialog;
import website.openeng.anki.dialogs.TagsDialog;
import website.openeng.anki.dialogs.TagsDialog.TagsDialogListener;
import website.openeng.anki.stats.AnkiStatsTaskHandler;
import website.openeng.anki.stats.ChartView;
import website.openeng.async.CollectionLoader;
import website.openeng.async.DeckTask;
import website.openeng.libanki.Collection;
import website.openeng.libanki.Consts;
import website.openeng.libanki.Utils;
import website.openeng.themes.StyledProgressDialog;
import website.openeng.themes.Themes;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import timber.log.Timber;

public class StudyOptionsFragment extends Fragment implements LoaderManager.LoaderCallbacks<Collection> {

    /**
     * Available options performed by other activities
     */
    public static final int PREFERENCES_UPDATE = 0;
    private static final int REQUEST_REVIEW = 1;
    private static final int ADD_NOTE = 2;
    private static final int BROWSE_CARDS = 3;
    private static final int STATISTICS = 4;
    private static final int DECK_OPTIONS = 5;

    /**
     * Constants for selecting which content view to display
     */
    public static final int CONTENT_STUDY_OPTIONS = 0;
    public static final int CONTENT_CONGRATS = 1;

    // Threshold at which the total number of new cards is truncated by libanki
    private static final int NEW_CARD_COUNT_TRUNCATE_THRESHOLD = 1000;

    /**
     * Preferences
     */
    private int mCurrentContentView = CONTENT_STUDY_OPTIONS;
    boolean mInvertedColors = false;

    /** Alerts to inform the user about different situations */
    private MaterialDialog mProgressDialog;

    /**
     * UI elements for "Study Options" view
     */
    private View mStudyOptionsView;
    private View mDeckInfoLayout;
    private Button mButtonStart;
    private Button mButtonCustomStudy;
    private Button mButtonUnbury;
    private TextView mTextDeckName;
    private TextView mTextDeckDescription;
    private TextView mTextTodayNew;
    private TextView mTextTodayLrn;
    private TextView mTextTodayRev;
    private TextView mTextNewTotal;
    private TextView mTextTotal;
    private TextView mTextETA;
    private Button mDeckOptions;
    private Button mCramOptions;
    private TextView mTextCongratsMessage;
    private View mCongratsLayout;
    private ChartView mChartView;
    private String mSearchTerms;

    // Flag to indicate if the fragment should load the deck options immediately after it loads
    private boolean mLoadWithDeckOptions;

    private boolean mFragmented;
    private Collection mCollection;

    private Thread mFullNewCountThread = null;

    StudyOptionsListener mListener;

    public interface StudyOptionsListener {
        public void onRequireDeckListUpdate();

        public void createFilteredDeck(JSONArray delays, Object[] terms, Boolean resched);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            mListener = (StudyOptionsListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement StudyOptionsListener");
        }
    }

    /**
     * Callbacks for UI events
     */
    private View.OnClickListener mButtonClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Collection col = getCol();
            // long timeLimit = 0;
            switch (v.getId()) {
            case R.id.studyoptions_start:
                Timber.i("StudyOptionsFragment:: start study button pressed");
                openReviewer();
                return;
            case R.id.studyoptions_custom:
                Timber.i("StudyOptionsFragment:: custom study button pressed");
                showCustomStudyContextMenu();
                return;
            case R.id.studyoptions_unbury:
            case R.id.studyoptions_unbury_cram:
                Timber.i("StudyOptionsFragment:: unbury button pressed");
                col.getSched().unburyCardsForDeck();
                refreshInterfaceAndDecklist(true);
                return;
            case R.id.studyoptions_options_cram:
                Timber.i("StudyOptionsFragment:: cram deck options button pressed");
                openFilteredDeckOptions();
                return;
            case R.id.studyoptions_options:
                Timber.i("StudyOptionsFragment:: deck options button pressed");
                Intent i = new Intent(getActivity(), DeckOptions.class);
                startActivityForResult(i, DECK_OPTIONS);
                ActivityTransitionAnimation.slide(getActivity(), ActivityTransitionAnimation.FADE);
                return;
            case R.id.studyoptions_rebuild_cram:
                Timber.i("StudyOptionsFragment:: rebuild cram deck button pressed");
                mProgressDialog = StyledProgressDialog.show(getActivity(), "",
                        getResources().getString(R.string.rebuild_cram_deck), true);
                DeckTask.launchDeckTask(DeckTask.TASK_TYPE_REBUILD_CRAM, getDeckTaskListener(true),
                        new DeckTask.TaskData(mFragmented));
                return;
            case R.id.studyoptions_empty_cram:
                Timber.i("StudyOptionsFragment:: empty cram deck button pressed");
                mProgressDialog = StyledProgressDialog.show(getActivity(), "",
                        getResources().getString(R.string.empty_cram_deck), false);
                DeckTask.launchDeckTask(DeckTask.TASK_TYPE_EMPTY_CRAM, getDeckTaskListener(true),
                        new DeckTask.TaskData(mFragmented));
                return;
            default:
            }
        }
    };

    private void openFilteredDeckOptions() {
        openFilteredDeckOptions(false);
    }

    /**
     * Open the FilteredDeckOptions activity to allow the user to modify the parameters of the
     * filtered deck.
     * @param defaultConfig If true, signals to the FilteredDeckOptions activity that the filtered
     *                      deck has no options associated with it yet and should use a default
     *                      set of values.
     */
    private void openFilteredDeckOptions(boolean defaultConfig) {
        Intent i = new Intent(getActivity(), FilteredDeckOptions.class);
        i.putExtra("defaultConfig", defaultConfig);
        startActivityForResult(i, DECK_OPTIONS);
        ActivityTransitionAnimation.slide(getActivity(), ActivityTransitionAnimation.FADE);
    }

    /**
     * Get a new instance of the fragment.
     * @param withDeckOptions If true, the fragment will load a new activity on top of itself
     *                        which shows the current deck's options. Set to true when programmatically
     *                        opening a new filtered deck for the first time.
     */
    public static StudyOptionsFragment newInstance(boolean withDeckOptions) {
        StudyOptionsFragment f = new StudyOptionsFragment();
        Bundle args = new Bundle();
        args.putBoolean("withDeckOptions", withDeckOptions);
        f.setArguments(args);
        return f;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        if (container == null) {
            // Currently in a layout without a container, so no reason to create our view.
            return null;
        }
        restorePreferences();
        mStudyOptionsView = inflater.inflate(R.layout.studyoptions_fragment, container, false);
        mFragmented = getActivity().getClass() != StudyOptionsActivity.class;
        NavigationDrawerActivity.setIsWholeCollection(false);
        startLoadingCollection();
        return mStudyOptionsView;
    }

    // Called when the collection loader has finished
    // NOTE: Fragment transactions are NOT allowed to be called from here onwards
    private void onCollectionLoaded(Collection col) {
        mCollection = col;
        initAllContentViews();
        if (getArguments() != null) {
            mLoadWithDeckOptions = getArguments().getBoolean("withDeckOptions");
        }
        refreshInterface(true);
        setHasOptionsMenu(true);
        ((AnkiActivity) getActivity()).hideProgressBar();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mFullNewCountThread != null) {
            mFullNewCountThread.interrupt();
        }
        Timber.d("onDestroy()");
    }

    @Override
    public void onResume() {
        super.onResume();
        if (colOpen()) {
            Timber.d("onResume()");
            refreshInterface(true);
        }
    }

    private void closeStudyOptions(int result) {
        Activity a = getActivity();
        if (!mFragmented && a != null) {
            a.setResult(result);
            a.finish();
            ActivityTransitionAnimation.slide(a, ActivityTransitionAnimation.RIGHT);
        } else if (a == null) {
            // getActivity() can return null if reference to fragment lingers after parent activity has been closed,
            // which is particularly relevant when using AsyncTasks.
            Timber.e("closeStudyOptions() failed due to getActivity() returning null");
        }
    }

    private void openReviewer() {
        Intent reviewer = new Intent(getActivity(), Reviewer.class);
        startActivityForResult(reviewer, REQUEST_REVIEW);
        animateLeft();
        getCol().startTimebox();
    }

    private void addNote() {
        Preferences.COMING_FROM_ADD = true;
        Intent intent = new Intent(getActivity(), NoteEditor.class);
        intent.putExtra(NoteEditor.EXTRA_CALLER, NoteEditor.CALLER_STUDYOPTIONS);
        startActivityForResult(intent, ADD_NOTE);
        animateLeft();
    }

    private void animateLeft() {
        ActivityTransitionAnimation.slide(getActivity(), ActivityTransitionAnimation.LEFT);
    }

    private void initAllContentViews() {
        // The fragmented view contains a chart
        if (mFragmented) {
            mChartView = (ChartView) mStudyOptionsView.findViewById(R.id.chart_view_small_chart);
            // Since the chart takes a while to build, we start with it hidden and fade it into
            // view later once it has finished building.
            mChartView.setVisibility(View.INVISIBLE);
        }

        mDeckInfoLayout = mStudyOptionsView.findViewById(R.id.studyoptions_deckinformation);
        mTextDeckName = (TextView) mStudyOptionsView.findViewById(R.id.studyoptions_deck_name);
        mTextDeckDescription = (TextView) mStudyOptionsView.findViewById(R.id.studyoptions_deck_description);
        // make links clickable
        mTextDeckDescription.setMovementMethod(LinkMovementMethod.getInstance());
        mButtonStart = (Button) mStudyOptionsView.findViewById(R.id.studyoptions_start);
        mButtonCustomStudy = (Button) mStudyOptionsView.findViewById(R.id.studyoptions_custom);
        mDeckOptions = (Button) mStudyOptionsView.findViewById(R.id.studyoptions_options);
        mCramOptions = (Button) mStudyOptionsView.findViewById(R.id.studyoptions_options_cram);
        mCongratsLayout = mStudyOptionsView.findViewById(R.id.studyoptions_congrats_layout);
        mTextCongratsMessage = (TextView) mStudyOptionsView.findViewById(R.id.studyoptions_congrats_message);

        if (getCol().getDecks().isDyn(getCol().getDecks().selected())) {
            Button rebBut = (Button) mStudyOptionsView.findViewById(R.id.studyoptions_rebuild_cram);
            rebBut.setOnClickListener(mButtonClickListener);
            Button emptyBut = (Button) mStudyOptionsView.findViewById(R.id.studyoptions_empty_cram);
            emptyBut.setOnClickListener(mButtonClickListener);
            // Enable the dynamic deck buttons and disable the normal ones
            ((LinearLayout) mStudyOptionsView.findViewById(R.id.studyoptions_cram_buttons))
                    .setVisibility(View.VISIBLE);
            ((LinearLayout) mStudyOptionsView.findViewById(R.id.studyoptions_regular_buttons))
                    .setVisibility(View.GONE);
            // Dynamic decks have their own unbury button to keep a reference to
            mButtonUnbury = (Button) mStudyOptionsView.findViewById(R.id.studyoptions_unbury_cram);
        } else {
            mButtonUnbury = (Button) mStudyOptionsView.findViewById(R.id.studyoptions_unbury);
        }
        mButtonUnbury.setOnClickListener(mButtonClickListener);
        // Code common to both fragmented and non-fragmented view
        mTextTodayNew = (TextView) mStudyOptionsView.findViewById(R.id.studyoptions_new);
        mTextTodayLrn = (TextView) mStudyOptionsView.findViewById(R.id.studyoptions_lrn);
        mTextTodayRev = (TextView) mStudyOptionsView.findViewById(R.id.studyoptions_rev);
        mTextNewTotal = (TextView) mStudyOptionsView.findViewById(R.id.studyoptions_total_new);
        mTextTotal = (TextView) mStudyOptionsView.findViewById(R.id.studyoptions_total);
        mTextETA = (TextView) mStudyOptionsView.findViewById(R.id.studyoptions_eta);
        mButtonStart.setOnClickListener(mButtonClickListener);
        mButtonCustomStudy.setOnClickListener(mButtonClickListener);
        mDeckOptions.setOnClickListener(mButtonClickListener);
        mCramOptions.setOnClickListener(mButtonClickListener);

        /*mFloatingActionButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            addNote();
        }
        });*/
    }

    /**
     * Special method to show the context menu for the custom study options
     * TODO: Turn this into a DialogFragment
     */
    private void showCustomStudyContextMenu() {
        Resources res = getResources();
        Drawable icon = res.getDrawable(R.drawable.ic_sort_black_36dp);
        icon.setAlpha(Themes.ALPHA_ICON_ENABLED_DARK);
        MaterialDialog dialog = new MaterialDialog.Builder(this.getActivity())
                .title(res.getString(R.string.custom_study)).icon(icon).cancelable(true)
                .items(res.getStringArray(R.array.custom_study_options_labels))
                .itemsCallback(new MaterialDialog.ListCallback() {
                    @Override
                    public void onSelection(MaterialDialog materialDialog, View view, int which,
                            CharSequence charSequence) {
                        DialogFragment dialogFragment;
                        if (which == CustomStudyDialog.CUSTOM_STUDY_TAGS) {
                            /*
                             * This is a special Dialog for CUSTOM STUDY, where instead of only collecting a
                             * number, it is necessary to collect a list of tags. This case handles the creation
                             * of that Dialog.
                             */
                            dialogFragment = website.openeng.anki.dialogs.TagsDialog.newInstance(
                                    TagsDialog.TYPE_CUSTOM_STUDY_TAGS, new ArrayList<String>(),
                                    new ArrayList<String>(getCol().getTags().all()));

                            ((TagsDialog) dialogFragment).setTagsDialogListener(new TagsDialogListener() {
                                @Override
                                public void onPositive(List<String> selectedTags, int option) {
                                    /*
                                     * Here's the method that gathers the final selection of tags, type of cards and generates the search
                                     * screen for the custom study deck.
                                     */
                                    StringBuilder sb = new StringBuilder();
                                    switch (option) {
                                    case 1:
                                        sb.append("is:new ");
                                        break;
                                    case 2:
                                        sb.append("is:due ");
                                        break;
                                    default:
                                        // Logging here might be appropriate : )
                                        break;
                                    }
                                    List<String> arr = new ArrayList<>();
                                    if (selectedTags.size() > 0) {
                                        for (String tag : selectedTags) {
                                            arr.add(String.format("tag:'%s'", tag));
                                        }
                                        sb.append("(" + TextUtils.join(" or ", arr) + ")");
                                    }
                                    mSearchTerms = sb.toString();
                                    createFilteredDeck(new JSONArray(),
                                            new Object[] { mSearchTerms, Consts.DYN_MAX_SIZE, Consts.DYN_RANDOM },
                                            false);
                                }
                            });
                        } else {
                            // Show CustomStudyDialog for all options other than the tags dialog
                            dialogFragment = CustomStudyDialog.newInstance(which);
                            // If we increase limits, refresh the interface to reflect the new counts
                            ((CustomStudyDialog) dialogFragment).setCustomStudyDialogListener(
                                    new CustomStudyDialog.CustomStudyDialogListener() {
                                        @Override
                                        public void onPositive(int option) {
                                            if (option == CustomStudyDialog.CUSTOM_STUDY_NEW
                                                    || option == CustomStudyDialog.CUSTOM_STUDY_REV) {
                                                refreshInterfaceAndDecklist(true);
                                            }
                                        }
                                    });
                        }
                        // Show the DialogFragment via Activity
                        ((AnkiActivity) getActivity()).showDialogFragment(dialogFragment);
                    }
                }).build();
        dialog.setOwnerActivity(getActivity());
        dialog.show();
    }

    public void createFilteredDeck(JSONArray delays, Object[] terms, Boolean resched) {
        JSONObject dyn;
        if (colOpen()) {
            Collection col = getCol();
            try {
                String deckName = col.getDecks().current().getString("name");
                String customStudyDeck = getResources().getString(R.string.custom_study_deck_name);
                JSONObject cur = col.getDecks().byName(customStudyDeck);
                if (cur != null) {
                    if (cur.getInt("dyn") != 1) {
                        new MaterialDialog.Builder(getActivity()).content(R.string.custom_study_deck_exists)
                                .negativeText(R.string.dialog_cancel).build().show();
                        return;
                    } else {
                        // safe to empty
                        col.getSched().emptyDyn(cur.getLong("id"));
                        // reuse; don't delete as it may have children
                        dyn = cur;
                        col.getDecks().select(cur.getLong("id"));
                    }
                } else {
                    long did = col.getDecks().newDyn(customStudyDeck);
                    dyn = col.getDecks().get(did);
                }
                // and then set various options
                if (delays.length() > 0) {
                    dyn.put("delays", delays);
                } else {
                    dyn.put("delays", JSONObject.NULL);
                }
                JSONArray ar = dyn.getJSONArray("terms");
                ar.getJSONArray(0).put(0,
                        new StringBuilder("deck:\"").append(deckName).append("\" ").append(terms[0]).toString());
                ar.getJSONArray(0).put(1, terms[1]);
                ar.getJSONArray(0).put(2, terms[2]);
                dyn.put("resched", resched);

                // Initial rebuild
                mProgressDialog = StyledProgressDialog.show(getActivity(), "",
                        getResources().getString(R.string.rebuild_custom_study_deck), false);
                DeckTask.launchDeckTask(DeckTask.TASK_TYPE_REBUILD_CRAM, getDeckTaskListener(true),
                        new DeckTask.TaskData(mFragmented));
            } catch (JSONException e) {
                throw new RuntimeException(e);
            }
        }
    }

    void setFragmentContentView(View newView) {
        ViewGroup parent = (ViewGroup) this.getView();
        parent.removeAllViews();
        parent.addView(newView);
    }

    private void updateChart(double[][] serieslist) {
        if (mChartView != null) {
            Collection col = CollectionHelper.getInstance().getCol(getActivity());
            AnkiStatsTaskHandler.createSmallDueChartChart(col, serieslist, mChartView);
            if (mChartView.getVisibility() == View.INVISIBLE) {
                mChartView.setVisibility(View.VISIBLE);
                mChartView.setAnimation(ViewAnimation.fade(ViewAnimation.FADE_IN, 500, 0));
            }
        }
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        getActivity().getMenuInflater().inflate(R.menu.study_options_fragment, menu);

        if (!getCol().undoAvailable()) {
            menu.findItem(R.id.action_undo).setVisible(false);
        } else {
            menu.findItem(R.id.action_undo).setVisible(true);
            Resources res = KanjiDroidApp.getAppResources();
            menu.findItem(R.id.action_undo)
                    .setTitle(res.getString(R.string.studyoptions_congrats_undo, getCol().undoName(res)));
        }
        super.onCreateOptionsMenu(menu, inflater);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.action_undo:
            Timber.i("StudyOptionsFragment:: Undo button pressed");
            if (colOpen()) {
                getCol().undo();
                refreshInterfaceAndDecklist(true);
                getActivity().supportInvalidateOptionsMenu();
            }
            return true;
        case R.id.action_add_note_from_study_options:
            Timber.i("StudyOptionsFragment:: Add note button pressed");
            addNote();
            return true;

        default:
            return super.onOptionsItemSelected(item);

        }
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent intent) {
        super.onActivityResult(requestCode, resultCode, intent);
        Timber.d("onActivityResult (requestCode = %d, resultCode = %d)", requestCode, resultCode);

        // rebuild action bar
        getActivity().supportInvalidateOptionsMenu();

        // boot back to deck picker if there was an error
        if (resultCode == DeckPicker.RESULT_DB_ERROR || resultCode == DeckPicker.RESULT_MEDIA_EJECTED) {
            closeStudyOptions(resultCode);
            return;
        }

        // check that the collection is open before doing anything
        if (!colOpen()) {
            startLoadingCollection();
            return;
        }

        // perform some special actions depending on which activity we're returning from
        if (requestCode == STATISTICS || requestCode == BROWSE_CARDS) {
            // select original deck if the statistics or card browser were opened,
            // which can change the selected deck
            if (intent.hasExtra("originalDeck")) {
                getCol().getDecks().select(intent.getLongExtra("originalDeck", 0L));
            }
        }
        if (requestCode == DECK_OPTIONS) {
            if (mLoadWithDeckOptions == true) {
                mLoadWithDeckOptions = false;
                try {
                    JSONObject deck = getCol().getDecks().current();
                    if (deck.getInt("dyn") != 0 && deck.has("empty")) {
                        deck.remove("empty");
                    }
                } catch (JSONException e) {
                    throw new RuntimeException(e);
                }
                mProgressDialog = StyledProgressDialog.show(getActivity(), "",
                        getResources().getString(R.string.rebuild_cram_deck), true);
                DeckTask.launchDeckTask(DeckTask.TASK_TYPE_REBUILD_CRAM, getDeckTaskListener(true),
                        new DeckTask.TaskData(mFragmented));
            } else {
                DeckTask.waitToFinish();
                refreshInterface(true);
            }
        } else if (requestCode == REQUEST_REVIEW) {
            if (resultCode == Reviewer.RESULT_NO_MORE_CARDS) {
                // If no more cards getting returned while counts > 0 then show a toast
                int[] counts = getCol().getSched().counts();
                if ((counts[0] + counts[1] + counts[2]) > 0) {
                    Toast.makeText(getActivity(), R.string.studyoptions_no_cards_due, Toast.LENGTH_LONG).show();
                }
            }
        } else if (requestCode == STATISTICS && mCurrentContentView == CONTENT_CONGRATS) {
            mCurrentContentView = CONTENT_STUDY_OPTIONS;
            setFragmentContentView(mStudyOptionsView);
        }
    }

    private void dismissProgressDialog() {
        // for rebuilding cram decks
        if (mProgressDialog != null && mProgressDialog.isShowing()) {
            try {
                mProgressDialog.dismiss();
            } catch (Exception e) {
                Timber.e("onPostExecute - Dialog dismiss Exception = " + e.getMessage());
            }
        }
    }

    public SharedPreferences restorePreferences() {
        SharedPreferences preferences = KanjiDroidApp.getSharedPrefs(getActivity().getBaseContext());
        return preferences;
    }

    private void refreshInterfaceAndDecklist(boolean resetSched) {
        refreshInterface(resetSched, true);
    }

    protected void refreshInterface() {
        refreshInterface(false, false);
    }

    protected void refreshInterface(boolean resetSched) {
        refreshInterface(resetSched, false);
    }

    /**
     * Rebuild the fragment's interface to reflect the status of the currently selected deck.
     *
     * @param resetSched    Indicates whether to rebuild the queues as well. Set to true for any
     *                      task that modifies queues (e.g., unbury or empty filtered deck).
     * @param resetDecklist Indicates whether to call back to the parent activity in order to
     *                      also refresh the deck list.
     */
    protected void refreshInterface(boolean resetSched, boolean resetDecklist) {
        // Exit if collection not open
        if (!colOpen()) {
            Timber.e("StudyOptionsFragment.refreshInterface failed due to Collection being closed");
            return;
        }
        Timber.d("Refreshing StudyOptionsFragment");
        // Load the deck counts for the deck from Collection asynchronously
        DeckTask.launchDeckTask(DeckTask.TASK_TYPE_UPDATE_VALUES_FROM_DECK, getDeckTaskListener(resetDecklist),
                new DeckTask.TaskData(new Object[] { resetSched, mChartView != null }));
    }

    /**
     * Returns a listener that rebuilds the interface after execute.
     *
     * @param refreshDecklist If true, the listener notifies the parent activity to update its deck list
     *                        to reflect the latest values.
     */
    private DeckTask.TaskListener getDeckTaskListener(final boolean refreshDecklist) {
        return new DeckTask.TaskListener() {
            @Override
            public void onPreExecute() {

            }

            @Override
            public void onPostExecute(DeckTask.TaskData result) {
                dismissProgressDialog();
                if (result != null) {
                    // Get the return values back from the AsyncTask
                    Object[] obj = result.getObjArray();
                    int newCards = (Integer) obj[0];
                    int lrnCards = (Integer) obj[1];
                    int revCards = (Integer) obj[2];
                    int totalNew = (Integer) obj[3];
                    int totalCards = (Integer) obj[4];
                    int eta = (Integer) obj[7];
                    double[][] serieslist = (double[][]) obj[8];

                    // Don't do anything if the fragment is no longer attached to it's Activity or col has been closed
                    if (getActivity() == null || !colOpen()) {
                        Timber.e("StudyOptionsFragment.mRefreshFragmentListener :: can't refresh");
                        return;
                    }
                    // Reinitialize controls incase changed to filtered deck
                    initAllContentViews();
                    // Set the deck name
                    String fullName;
                    JSONObject deck = getCol().getDecks().current();
                    try {
                        // Main deck name
                        fullName = deck.getString("name");
                        String[] name = fullName.split("::");
                        StringBuilder nameBuilder = new StringBuilder();
                        if (name.length > 0) {
                            nameBuilder.append(name[0]);
                        }
                        if (name.length > 1) {
                            nameBuilder.append("\n").append(name[1]);
                        }
                        if (name.length > 3) {
                            nameBuilder.append("...");
                        }
                        if (name.length > 2) {
                            nameBuilder.append("\n").append(name[name.length - 1]);
                        }
                        mTextDeckName.setText(nameBuilder.toString());
                        // Also set deck name in activity title in action bar if not tablet mode
                        if (!mFragmented) {
                            getActivity().setTitle(getResources().getString(R.string.studyoptions_title));
                            List<String> parts = Arrays.asList(fullName.split("::"));
                            ((AppCompatActivity) getActivity()).getSupportActionBar()
                                    .setSubtitle(parts.get(parts.size() - 1));
                        }
                    } catch (JSONException e) {
                        throw new RuntimeException(e);
                    }

                    // open cram deck option if deck is opened for the first time
                    if (mLoadWithDeckOptions == true) {
                        openFilteredDeckOptions(mLoadWithDeckOptions);
                        return;
                    }

                    // Switch between the ordinary view and "congratulations" view
                    if (newCards + lrnCards + revCards == 0) {
                        mCurrentContentView = CONTENT_CONGRATS;
                        mDeckInfoLayout.setVisibility(View.GONE);
                        mCongratsLayout.setVisibility(View.VISIBLE);
                        mTextCongratsMessage.setText(getCol().getSched().finishedMsg(getActivity()));
                        mButtonStart.setVisibility(View.GONE);
                    } else {
                        mCurrentContentView = CONTENT_STUDY_OPTIONS;
                        mDeckInfoLayout.setVisibility(View.VISIBLE);
                        mCongratsLayout.setVisibility(View.GONE);
                        mButtonStart.setVisibility(View.VISIBLE);
                    }

                    // Set deck description
                    String desc;
                    try {
                        if (deck.getInt("dyn") == 0) {
                            desc = getCol().getDecks().getActualDescription();
                        } else {
                            desc = getResources().getString(R.string.dyn_deck_desc);
                        }
                    } catch (JSONException e) {
                        throw new RuntimeException(e);
                    }
                    if (desc.length() > 0) {
                        mTextDeckDescription.setText(Html.fromHtml(desc));
                        mTextDeckDescription.setVisibility(View.VISIBLE);
                    } else {
                        mTextDeckDescription.setVisibility(View.GONE);
                    }

                    // Update the chart (for tablet view)
                    updateChart(serieslist);

                    // Set new/learn/review card counts
                    mTextTodayNew.setText(String.valueOf(newCards));
                    mTextTodayLrn.setText(String.valueOf(lrnCards));
                    mTextTodayRev.setText(String.valueOf(revCards));

                    // Set the total number of new cards in deck
                    if (totalNew < NEW_CARD_COUNT_TRUNCATE_THRESHOLD) {
                        // if it hasn't been truncated by libanki then just set it usually
                        mTextNewTotal.setText(String.valueOf(totalNew));
                    } else {
                        // if truncated then make a thread to allow full count to load
                        mTextNewTotal.setText(">1000");
                        if (mFullNewCountThread != null) {
                            // a thread was previously made -- interrupt it
                            mFullNewCountThread.interrupt();
                        }
                        mFullNewCountThread = new Thread(new Runnable() {
                            @Override
                            public void run() {
                                Collection collection = getCol();
                                // TODO: refactor code to not rewrite this query, add to Sched.totalNewForCurrentDeck()
                                StringBuilder sbQuery = new StringBuilder();
                                sbQuery.append("SELECT count(*) FROM cards WHERE did IN ");
                                sbQuery.append(Utils.ids2str(collection.getDecks().active()));
                                sbQuery.append(" AND queue = 0");
                                final int fullNewCount = collection.getDb().queryScalar(sbQuery.toString());
                                if (fullNewCount > 0) {
                                    Runnable setNewTotalText = new Runnable() {
                                        @Override
                                        public void run() {
                                            mTextNewTotal.setText(String.valueOf(fullNewCount));
                                        }
                                    };
                                    if (!Thread.currentThread().isInterrupted()) {
                                        mTextNewTotal.post(setNewTotalText);
                                    }
                                }
                            }
                        });
                        mFullNewCountThread.start();
                    }

                    // Set total number of cards
                    mTextTotal.setText(String.valueOf(totalCards));
                    // Set estimated time remaining
                    if (eta != -1) {
                        mTextETA.setText(Integer.toString(eta));
                    } else {
                        mTextETA.setText("-");
                    }

                    // Show unbury button if necessary
                    if (mButtonUnbury != null) {
                        if (getCol().getSched().haveBuried()) {
                            mButtonUnbury.setVisibility(View.VISIBLE);
                        } else {
                            mButtonUnbury.setVisibility(View.GONE);
                        }
                    }
                }

                // If in fragmented mode, refresh the deck list
                if (mFragmented && refreshDecklist) {
                    mListener.onRequireDeckListUpdate();
                }
            }

            @Override
            public void onProgressUpdate(DeckTask.TaskData... values) {

            }

            @Override
            public void onCancelled() {

            }
        };
    }

    private Collection getCol() {
        return mCollection;
    }

    private boolean colOpen() {
        return getCol() != null && getCol().getDb() != null;
    }

    // Method for loading the collection which is inherited by all AnkiActivitys
    protected void startLoadingCollection() {
        // Initialize the open collection loader
        Timber.d("startLoadingCollection()");
        AnkiActivity activity = (AnkiActivity) getActivity();
        activity.showProgressBar();
        getLoaderManager().initLoader(0, null, this);
    }

    // CollectionLoader Listener callbacks
    @Override
    public Loader<Collection> onCreateLoader(int id, Bundle args) {
        // Currently only using one loader, so ignore id
        return new CollectionLoader(getActivity());
    }

    @Override
    public void onLoadFinished(Loader<Collection> loader, Collection col) {
        if (col != null) {
            onCollectionLoaded(col);
        } else {
            AnkiDatabaseManager.closeDatabase(CollectionHelper.getCollectionPath(getActivity()));
            //showDialog(DIALOG_LOAD_FAILED);
        }
    }

    @Override
    public void onLoaderReset(Loader<Collection> arg0) {
        // We don't currently retain any references, so no need to free any data here
    }
}