/****************************************************************************************
* 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.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.support.v4.content.ContextCompat;
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.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);
mZeroCountColor = ta.getColor(0, ContextCompat.getColor(context, R.color.black));
mNewCountColor = ta.getColor(1, ContextCompat.getColor(context, R.color.black));
mLearnCountColor = ta.getColor(2, ContextCompat.getColor(context, R.color.black));
mReviewCountColor = ta.getColor(3, ContextCompat.getColor(context, R.color.black));
mRowCurrentDrawable = ta.getResourceId(4, 0);
mDeckNameDefaultColor = ta.getColor(5, ContextCompat.getColor(context, R.color.black));
mDeckNameDynColor = ta.getColor(6, ContextCompat.getColor(context, R.color.material_blue_A700));
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);
expander.setContentDescription(expander.getContext().getString(R.string.expand));
} else if (node.children.size() > 0) {
expander.setImageDrawable(mCollapseImage);
expander.setContentDescription(expander.getContext().getString(R.string.collapse));
} 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;
}
}