/*
* ******************************************************************************
* Copyright (c) 2013-2014 Gabriele Mariotti.
*
* 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 it.gmariotti.cardslib.library.internal;
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.os.Parcelable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import it.gmariotti.cardslib.library.R;
import it.gmariotti.cardslib.library.internal.base.BaseCardArrayAdapter;
import it.gmariotti.cardslib.library.view.CardListView;
import it.gmariotti.cardslib.library.view.base.CardViewWrapper;
import it.gmariotti.cardslib.library.view.CardView;
import it.gmariotti.cardslib.library.view.listener.SwipeDismissListViewTouchListener;
import it.gmariotti.cardslib.library.view.listener.SwipeOnScrollListener;
import it.gmariotti.cardslib.library.view.listener.UndoBarController;
import it.gmariotti.cardslib.library.view.listener.UndoCard;
import it.gmariotti.cardslib.library.view.listener.dismiss.DefaultDismissableManager;
import it.gmariotti.cardslib.library.view.listener.dismiss.Dismissable;
/**
* Array Adapter for {@link Card} model
* <p/>
* Usage:
* <pre><code>
* ArrayList<Card> cards = new ArrayList<Card>();
* for (int i=0;i<1000;i++){
* CardExample card = new CardExample(getActivity(),"My title "+i,"Inner text "+i);
* cards.add(card);
* }
*
* CardArrayAdapter mCardArrayAdapter = new CardArrayAdapter(getActivity(),cards);
*
* CardListView listView = (CardListView) getActivity().findViewById(R.id.listId);
* listView.setAdapter(mCardArrayAdapter); *
* </code></pre>
* It provides a default layout id for each row @layout/list_card_layout
* Use can easily customize it using card:list_card_layout_resourceID attr in your xml layout:
* <pre><code>
* <it.gmariotti.cardslib.library.view.CardListView
* android:layout_width="match_parent"
* android:layout_height="match_parent"
* android:id="@+id/carddemo_list_gplaycard"
* card:list_card_layout_resourceID="@layout/list_card_thumbnail_layout" />
* </code></pre>
* or:
* <pre><code>
* adapter.setRowLayoutId(list_card_layout_resourceID);
* </code></pre>
* </p>
* @author Gabriele Mariotti (gabri.mariotti@gmail.com)
*/
public class CardArrayAdapter extends BaseCardArrayAdapter implements UndoBarController.UndoListener {
protected static String TAG = "CardArrayAdapter";
/**
* {@link CardListView}
*/
protected CardListView mCardListView;
/**
* Listener invoked when a card is swiped
*/
protected SwipeDismissListViewTouchListener mOnTouchListener;
/**
* Used to enable an undo message after a swipe action
*/
protected boolean mEnableUndo=false;
/**
* Undo Controller
*/
protected UndoBarController mUndoBarController;
/**
* Internal Map with all Cards.
* It uses the card id value as key.
*/
protected HashMap<String /* id */,Card> mInternalObjects;
/**
* Dismissable Manager
*/
protected Dismissable mDismissable;
// -------------------------------------------------------------
// Constructors
// -------------------------------------------------------------
/**
* Constructor
*
* @param context The current context.
* @param cards The cards to represent in the ListView.
*/
public CardArrayAdapter(Context context, List<Card> cards) {
super(context, cards);
}
// -------------------------------------------------------------
// Views
// -------------------------------------------------------------
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
CardViewWrapper mCardView;
Card mCard;
LayoutInflater mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//Retrieve card from items
mCard = (Card) getItem(position);
if (mCard != null) {
int layout = mRowLayoutId;
boolean recycle = false;
//Inflate layout
if (view == null) {
recycle = false;
view = mInflater.inflate(layout, parent, false);
} else {
recycle = true;
}
//Setup card
mCardView = (CardViewWrapper) view.findViewById(R.id.list_cardId);
if (mCardView != null) {
//It is important to set recycle value for inner layout elements
mCardView.setForceReplaceInnerLayout(Card.equalsInnerLayout(mCardView.getCard(),mCard));
//It is important to set recycle value for performance issue
mCardView.setRecycle(recycle);
//Save original swipeable to prevent cardSwipeListener (listView requires another cardSwipeListener)
boolean origianlSwipeable = mCard.isSwipeable();
mCard.setSwipeable(false);
mCardView.setCard(mCard);
//Set originalValue
mCard.setSwipeable(origianlSwipeable);
//If card has an expandable button override animation
if ((mCard.getCardHeader() != null && mCard.getCardHeader().isButtonExpandVisible()) || mCard.getViewToClickToExpand()!=null ){
setupExpandCollapseListAnimation(mCardView);
}
//Setup swipeable animation
setupSwipeableAnimation(mCard, mCardView);
//setupMultiChoice
setupMultichoice(view,mCard,mCardView,position);
}
}
return view;
}
/**
* Sets SwipeAnimation on List
*
* @param card {@link Card}
* @param cardView {@link it.gmariotti.cardslib.library.view.base.CardViewWrapper}
*/
protected void setupSwipeableAnimation(final Card card, CardViewWrapper cardView) {
if (card.isSwipeable()){
if (mOnTouchListener == null){
mOnTouchListener = new SwipeDismissListViewTouchListener(mCardListView, mCallback);
//Configure the default DismissableManager
if (mDismissable == null) mDismissable = new DefaultDismissableManager();
mDismissable.setAdapter(this);
mOnTouchListener.setDismissable(mDismissable);
// Setting this scroll listener is required to ensure that during
// ListView scrolling, we don't look for swipes.
if (mCardListView.getOnScrollListener() == null){
SwipeOnScrollListener scrollListener = new SwipeOnScrollListener();
scrollListener.setTouchListener(mOnTouchListener);
mCardListView.setOnScrollListener(scrollListener);
}else{
AbsListView.OnScrollListener onScrollListener=mCardListView.getOnScrollListener();
if (onScrollListener instanceof SwipeOnScrollListener)
((SwipeOnScrollListener) onScrollListener).setTouchListener(mOnTouchListener);
}
mCardListView.setOnTouchListener(mOnTouchListener);
}
cardView.setOnTouchListener(mOnTouchListener);
}else{
//prevent issue with recycle view
cardView.setOnTouchListener(null);
}
}
/**
* Overrides the default collapse/expand animation in a List
*
* @param cardView {@link it.gmariotti.cardslib.library.view.base.CardViewWrapper}
*/
protected void setupExpandCollapseListAnimation(CardViewWrapper cardView) {
if (cardView == null) return;
cardView.setOnExpandListAnimatorListener(mCardListView);
}
// -------------------------------------------------------------
// SwipeListener and undo action
// -------------------------------------------------------------
/**
* Listener invoked when a card is swiped
*/
SwipeDismissListViewTouchListener.DismissCallbacks mCallback = new SwipeDismissListViewTouchListener.DismissCallbacks() {
@Override
public boolean canDismiss(int position, Card card) {
return mDismissable.isDismissable(position, card);
}
@Override
public void onDismiss(ListView listView, int[] reverseSortedPositions) {
int[] itemPositions=new int[reverseSortedPositions.length];
String[] itemIds=new String[reverseSortedPositions.length];
int i=0;
// Keep track of the cards that will be removed
final ArrayList<Card> removedCards = new ArrayList<Card>();
//Remove cards and notifyDataSetChanged
for (int position : reverseSortedPositions) {
Card card = null;
if (listView.getAdapter() != null && listView.getAdapter().getItem(position) instanceof Card)
card = (Card) listView.getAdapter().getItem(position);
//Card card = getItem(position);
if (card != null) {
itemPositions[i] = position;
itemIds[i] = card.getId();
i++;
/*
if (card.isExpanded()){
if (card.getCardView()!=null && card.getCardView().getOnExpandListAnimatorListener()!=null){
//There is a List Animator.
card.getCardView().getOnExpandListAnimatorListener().onCollapseStart(card.getCardView(), card.getCardView().getInternalExpandLayout());
}
}*/
removedCards.add(card);
remove(card);
if (card.getOnSwipeListener() != null) {
card.getOnSwipeListener().onSwipe(card);
}
}else{
Log.e(TAG,"Error on swipe action. Impossible to retrieve the card from position");
}
}
notifyDataSetChanged();
//Check for a undo message to confirm
if (isEnableUndo() && mUndoBarController!=null){
//Show UndoBar
UndoCard itemUndo=new UndoCard(itemPositions,itemIds);
//MessageUndoBar
String messageUndoBar=null;
if (getUndoBarController().getUndoBarUIElements()!=null){
messageUndoBar = getUndoBarController().getUndoBarUIElements().getMessageUndo(CardArrayAdapter.this,itemIds,itemPositions);
}
//Default message if null
if (messageUndoBar == null) {
if (getContext() != null) {
Resources res = getContext().getResources();
if (res != null) {
messageUndoBar = res.getQuantityString(R.plurals.list_card_undo_items, reverseSortedPositions.length, reverseSortedPositions.length);
}
}
}
mUndoBarController.showUndoBar(
false,
messageUndoBar,
itemUndo,
new UndoBarController.UndoBarHideListener() {
@Override
public void onUndoBarHide(boolean undoOccurred) {
// Remove the items from mInternalObjects, if
// the undo was not triggered, since they are
// now permanently removed from the underlying Array.
if (!undoOccurred) {
for (Card card : removedCards) {
if (card.getOnUndoHideSwipeListListener()!=null)
card.getOnUndoHideSwipeListListener().onUndoHideSwipe(card);
mInternalObjects.remove(card.getId());
}
}
}
});
}
}
};
// -------------------------------------------------------------
// Undo Default Listener
// -------------------------------------------------------------
@Override
public void onUndo(Parcelable token) {
//Restore items in lists (use reverseSortedOrder)
if (token != null) {
UndoCard item = (UndoCard) token;
int[] itemPositions = item.itemPosition;
String[] itemIds = item.itemId;
if (itemPositions != null) {
int end = itemPositions.length;
for (int i = end - 1; i >= 0; i--) {
int itemPosition = itemPositions[i];
String id= itemIds[i];
if (id==null){
Log.w(TAG, "You have to set a id value to use the undo action");
}else{
Card card = mInternalObjects.get(id);
if (card!=null){
insert(card, itemPosition);
notifyDataSetChanged();
if (card.getOnUndoSwipeListListener()!=null)
card.getOnUndoSwipeListListener().onUndoSwipe(card);
}
}
}
}
}
}
/**
* Indicates if the undo message is enabled after a swipe action
*
* @return <code>true</code> if the undo message is enabled
*/
public boolean isEnableUndo() {
return mEnableUndo;
}
/**
* Enables an undo message after a swipe action
*
* @param enableUndo <code>true</code> to enable an undo message
*/
public void setEnableUndo(boolean enableUndo) {
mEnableUndo = enableUndo;
if (enableUndo) {
mInternalObjects = new HashMap<String, Card>();
for (int i=0;i<getCount();i++) {
Card card = getItem(i);
mInternalObjects.put(card.getId(), card);
}
//Create a UndoController
if (mUndoBarController==null){
if (mUndoBarUIElements==null)
mUndoBarUIElements=new UndoBarController.DefaultUndoBarUIElements();
if (mContext!=null && mContext instanceof Activity) {
View undobar = ((Activity) mContext).findViewById(mUndoBarUIElements.getUndoBarId());
if (undobar != null) {
mUndoBarController = new UndoBarController(undobar, this, mUndoBarUIElements);
}
}else{
Log.e(TAG,"Undo Action requires a valid Activity context");
throw new IllegalArgumentException("Undo Action requires a valid Activity context");
}
}
}else{
mUndoBarController=null;
}
}
// ---------------------------------------------------------------------
// Override Array Manipulation Methods To Keep mInternalObjects in Sync
// ---------------------------------------------------------------------
// public void remove() intentionally omitted, since mInternalObjects needs
// to keep a reference so the remove can be undone, if necessary.
@Override
public void add(Card card) {
super.add(card);
if (mEnableUndo) {
mInternalObjects.put(card.getId(), card);
}
}
@Override
public void addAll(Collection<? extends Card> cardCollection) {
super.addAll(cardCollection);
if (mEnableUndo) {
for (Card card : cardCollection) {
mInternalObjects.put(card.getId(), card);
}
}
}
@Override
public void addAll(Card...cards) {
super.addAll(cards);
if (mEnableUndo) {
for (Card card : cards) {
mInternalObjects.put(card.getId(), card);
}
}
}
@Override
public void clear() {
super.clear();
if (mEnableUndo) {
mInternalObjects.clear();
}
}
@Override
public void insert(Card card, int index) {
super.insert(card, index);
if (mEnableUndo) {
mInternalObjects.put(card.getId(), card);
}
}
// -------------------------------------------------------------
// Getters and Setters
// -------------------------------------------------------------
/**
* @return {@link CardListView}
*/
public CardListView getCardListView() {
return mCardListView;
}
/**
* Sets the {@link CardListView}
*
* @param cardListView cardListView
*/
public void setCardListView(CardListView cardListView) {
this.mCardListView = cardListView;
}
/**
* Return the UndoBarController for undo action
*
* @return {@link UndoBarController}
*/
public UndoBarController getUndoBarController() {
return mUndoBarController;
}
/**
* Sets a custom DismissableManager
* @param dismissable
*/
public void setDismissable(Dismissable dismissable) {
mDismissable = dismissable;
}
}