com.ichi2.anki.widgets.DeckAdapter.java Source code

Java tutorial

Introduction

Here is the source code for com.ichi2.anki.widgets.DeckAdapter.java

Source

/****************************************************************************************
 * Copyright (c) 2015 Houssam Salem <houssam.salem.au@gmail.com>                        *
 *                                                                                      *
 * 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 com.ichi2.anki.widgets;

import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.ichi2.anki.R;
import com.ichi2.compat.Compat;
import com.ichi2.compat.CompatHelper;
import com.ichi2.libanki.Collection;
import com.ichi2.libanki.Sched;

import org.json.JSONObject;

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

public class DeckAdapter extends RecyclerView.Adapter<DeckAdapter.ViewHolder> {

    private LayoutInflater mLayoutInflater;
    private List<Sched.DeckDueTreeNode> mDeckList;
    private int mZeroCountColor;
    private int mNewCountColor;
    private int mLearnCountColor;
    private int mReviewCountColor;
    private int mRowCurrentDrawable;
    private int mDeckNameDefaultColor;
    private int mDeckNameDynColor;
    private Drawable mExpandImage;
    private Drawable mCollapseImage;
    private Drawable mNoExpander = new ColorDrawable(Color.TRANSPARENT);

    // Listeners
    private View.OnClickListener mDeckClickListener;
    private View.OnClickListener mDeckExpanderClickListener;
    private View.OnLongClickListener mDeckLongClickListener;
    private View.OnClickListener mCountsClickListener;

    private Collection mCol;

    // Totals accumulated as each deck is processed
    private int mNew;
    private int mLrn;
    private int mRev;

    // Flags
    private boolean mHasSubdecks;

    // ViewHolder class to save inflated views for recycling
    public class ViewHolder extends RecyclerView.ViewHolder {
        public RelativeLayout deckLayout;
        public LinearLayout countsLayout;
        public ImageButton deckExpander;
        public ImageButton indentView;
        public TextView deckName;
        public TextView deckNew, deckLearn, deckRev;

        public ViewHolder(View v) {
            super(v);
            deckLayout = (RelativeLayout) v.findViewById(R.id.DeckPickerHoriz);
            countsLayout = (LinearLayout) v.findViewById(R.id.counts_layout);
            deckExpander = (ImageButton) v.findViewById(R.id.deckpicker_expander);
            indentView = (ImageButton) v.findViewById(R.id.deckpicker_indent);
            deckName = (TextView) v.findViewById(R.id.deckpicker_name);
            deckNew = (TextView) v.findViewById(R.id.deckpicker_new);
            deckLearn = (TextView) v.findViewById(R.id.deckpicker_lrn);
            deckRev = (TextView) v.findViewById(R.id.deckpicker_rev);
        }
    }

    public DeckAdapter(LayoutInflater layoutInflater, Context context) {
        mLayoutInflater = layoutInflater;
        mDeckList = new ArrayList<>();
        // Get the colors from the theme attributes
        int[] attrs = new int[] { R.attr.zeroCountColor, R.attr.newCountColor, R.attr.learnCountColor,
                R.attr.reviewCountColor, R.attr.currentDeckBackground, android.R.attr.textColor,
                R.attr.dynDeckColor, R.attr.expandRef, R.attr.collapseRef };
        TypedArray ta = context.obtainStyledAttributes(attrs);
        Resources res = context.getResources();
        mZeroCountColor = ta.getColor(0, res.getColor(R.color.zero_count));
        mNewCountColor = ta.getColor(1, res.getColor(R.color.new_count));
        mLearnCountColor = ta.getColor(2, res.getColor(R.color.learn_count));
        mReviewCountColor = ta.getColor(3, res.getColor(R.color.review_count));
        mRowCurrentDrawable = ta.getResourceId(4, 0);
        mDeckNameDefaultColor = ta.getColor(5, res.getColor(R.color.black));
        mDeckNameDynColor = ta.getColor(6, res.getColor(R.color.deckadapter_deck_name_dyn));
        mExpandImage = ta.getDrawable(7);
        mCollapseImage = ta.getDrawable(8);
        ta.recycle();
    }

    public void setDeckClickListener(View.OnClickListener listener) {
        mDeckClickListener = listener;
    }

    public void setCountsClickListener(View.OnClickListener listener) {
        mCountsClickListener = listener;
    }

    public void setDeckExpanderClickListener(View.OnClickListener listener) {
        mDeckExpanderClickListener = listener;
    }

    public void setDeckLongClickListener(View.OnLongClickListener listener) {
        mDeckLongClickListener = listener;
    }

    /**
     * Consume a list of {@link Sched.DeckDueTreeNode}s to render a new deck list.
     */
    public void buildDeckList(List<Sched.DeckDueTreeNode> nodes, Collection col) {
        mCol = col;
        mDeckList.clear();
        mNew = mLrn = mRev = 0;
        mHasSubdecks = false;
        processNodes(nodes);
        notifyDataSetChanged();
    }

    @Override
    public DeckAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = mLayoutInflater.inflate(R.layout.deck_item, parent, false);
        return new ViewHolder(v);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        // Update views for this node
        Sched.DeckDueTreeNode node = mDeckList.get(position);
        // Set the expander icon and padding according to whether or not there are any subdecks
        RelativeLayout deckLayout = holder.deckLayout;
        int rightPadding = (int) deckLayout.getResources().getDimension(R.dimen.deck_picker_right_padding);
        if (mHasSubdecks) {
            int smallPadding = (int) deckLayout.getResources().getDimension(R.dimen.deck_picker_left_padding_small);
            deckLayout.setPadding(smallPadding, 0, rightPadding, 0);
            holder.deckExpander.setVisibility(View.VISIBLE);
            // Create the correct expander for this deck
            setDeckExpander(holder.deckExpander, holder.indentView, node);
        } else {
            holder.deckExpander.setVisibility(View.GONE);
            int normalPadding = (int) deckLayout.getResources().getDimension(R.dimen.deck_picker_left_padding);
            deckLayout.setPadding(normalPadding, 0, rightPadding, 0);
        }

        if (node.children.size() > 0) {
            holder.deckExpander.setTag(node.did);
            holder.deckExpander.setOnClickListener(mDeckExpanderClickListener);
        } else {
            holder.deckExpander.setOnClickListener(null);
        }
        holder.deckLayout.setBackgroundResource(mRowCurrentDrawable);
        // Set background colour. The current deck has its own color
        if (node.did == mCol.getDecks().current().optLong("id")) {
            holder.deckLayout.setBackgroundResource(mRowCurrentDrawable);
        } else {
            CompatHelper.getCompat().setSelectableBackground(holder.deckLayout);
        }
        // Set deck name and colour. Filtered decks have their own colour
        holder.deckName.setText(node.names[0]);
        if (mCol.getDecks().isDyn(node.did)) {
            holder.deckName.setTextColor(mDeckNameDynColor);
        } else {
            holder.deckName.setTextColor(mDeckNameDefaultColor);
        }

        // Set the card counts and their colors
        holder.deckNew.setText(String.valueOf(node.newCount));
        holder.deckNew.setTextColor((node.newCount == 0) ? mZeroCountColor : mNewCountColor);
        holder.deckLearn.setText(String.valueOf(node.lrnCount));
        holder.deckLearn.setTextColor((node.lrnCount == 0) ? mZeroCountColor : mLearnCountColor);
        holder.deckRev.setText(String.valueOf(node.revCount));
        holder.deckRev.setTextColor((node.revCount == 0) ? mZeroCountColor : mReviewCountColor);

        // Store deck ID in layout's tag for easy retrieval in our click listeners
        holder.deckLayout.setTag(node.did);
        holder.countsLayout.setTag(node.did);

        // Set click listeners
        holder.deckLayout.setOnClickListener(mDeckClickListener);
        holder.deckLayout.setOnLongClickListener(mDeckLongClickListener);
        holder.countsLayout.setOnClickListener(mCountsClickListener);
    }

    @Override
    public int getItemCount() {
        return mDeckList.size();
    }

    private void setDeckExpander(ImageButton expander, ImageButton indent, Sched.DeckDueTreeNode node) {
        boolean collapsed = mCol.getDecks().get(node.did).optBoolean("collapsed", false);
        // Apply the correct expand/collapse drawable
        if (collapsed) {
            expander.setImageDrawable(mExpandImage);
        } else if (node.children.size() > 0) {
            expander.setImageDrawable(mCollapseImage);
        } else {
            expander.setImageDrawable(mNoExpander);
        }
        // Add some indenting for each nested level
        int width = (int) indent.getResources().getDimension(R.dimen.keyline_1) * node.depth;
        indent.setMinimumWidth(width);
    }

    private void processNodes(List<Sched.DeckDueTreeNode> nodes) {
        processNodes(nodes, 0);
    }

    private void processNodes(List<Sched.DeckDueTreeNode> nodes, int depth) {
        for (Sched.DeckDueTreeNode node : nodes) {
            // If the default deck is empty, hide it by not adding it to the deck list.
            // We don't hide it if it's the only deck or if it has sub-decks.
            if (node.did == 1 && nodes.size() > 1 && node.children.size() == 0) {
                if (mCol.getDb().queryScalar("select 1 from cards where did = 1") == 0) {
                    continue;
                }
            }
            // If any of this node's parents are collapsed, don't add it to the deck list
            for (JSONObject parent : mCol.getDecks().parents(node.did)) {
                mHasSubdecks = true; // If a deck has a parent it means it's a subdeck so set a flag
                if (parent.optBoolean("collapsed")) {
                    return;
                }
            }
            mDeckList.add(node);
            // Keep track of the depth. It's used to determine visual properties like indenting later
            node.depth = depth;

            // Add this node's counts to the totals if it's a parent deck
            if (depth == 0) {
                mNew += node.newCount;
                mLrn += node.lrnCount;
                mRev += node.revCount;
            }
            // Process sub-decks
            processNodes(node.children, depth + 1);
        }
    }

    /**
     * Return the position of the deck in the deck list. If the deck is a child of a collapsed deck
     * (i.e., not visible in the deck list), then the position of the parent deck is returned instead.
     *
     * An invalid deck ID will return position 0.
     */
    public int findDeckPosition(long did) {
        for (int i = 0; i < mDeckList.size(); i++) {
            if (mDeckList.get(i).did == did) {
                return i;
            }
        }
        // If the deck is not in our list, we search again using the immediate parent
        List<JSONObject> parents = mCol.getDecks().parents(did);
        if (parents.size() == 0) {
            return 0;
        } else {
            return findDeckPosition(parents.get(parents.size() - 1).optLong("id", 0));
        }
    }

    public int getEta() {
        return mCol.getSched().eta(new int[] { mNew, mLrn, mRev });
    }

    public int getDue() {
        return mNew + mLrn + mRev;
    }

    public List<Sched.DeckDueTreeNode> getDeckList() {
        return mDeckList;
    }
}