/*
* Copyright © 2016 TIBCO Software,Inc.All rights reserved.
* http://community.jaspersoft.com/project/jaspermobile-android
*
* Unless you have purchased a commercial license agreement from TIBCO Jaspersoft,
* the following license terms apply:
*
* This program is part of TIBCO Jaspersoft Mobile for Android.
*
* TIBCO Jaspersoft Mobile is free software:you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation,either version 3of the License,or
* (at your option)any later version.
*
* TIBCO Jaspersoft Mobile 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with TIBCO Jaspersoft Mobile for Android.If not,see
* <http://www.gnu.org/licenses/lgpl>.
*/
package com.jaspersoft.android.jaspermobile.activities.inputcontrols.adapters;
import android.support.v7.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
import rx.Observable;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action1;
import rx.functions.Func0;
import rx.schedulers.Schedulers;
import rx.subscriptions.Subscriptions;
/**
* RecyclerView adapter that support item filtering.
*
* @author Andrew Tivodar
* @since 2.2
*/
public abstract class FilterableAdapter<VH extends RecyclerView.ViewHolder, IT> extends RecyclerView.Adapter<VH> {
private static final int LIMIT = 50;
private int mOffset;
private List<IT> mFilteredItemList;
private List<IT> mItemsList;
private String mFilterWord;
private FilterListener mFilterListener;
private Subscription filterSubscription;
/**
* Create adapter using provided data set.
*
* @param valuesList item data set. Can not be null.
*/
public FilterableAdapter(List<IT> valuesList) {
if (valuesList == null) {
throw new IllegalArgumentException("Items list can not be null");
}
this.filterSubscription = Subscriptions.empty();
this.mFilterWord = "";
this.mItemsList = valuesList;
this.mFilteredItemList = new ArrayList<>();
loadNextItems();
}
public void setFilterListener(FilterListener filterListener) {
this.mFilterListener = filterListener;
}
/**
* Returns the total number of items in the data set hold by the adapter that match filter rule.
*
* @return The total number of items in this adapter that match filter rule.
*/
@Override
public final int getItemCount() {
return mFilteredItemList.size();
}
/**
* Returns item from the data set by index in filtered data set.
*
* @param position Adapter index - index in filtered data set, not in full one.
* @return item from data set
*/
public final IT getItem(int position) {
return mFilteredItemList.get(position);
}
/**
* Convert filtered position to position in full data set.
*
* @param adapterPosition Position in filtered data set.
* @return Position in full data set.
*/
public final int getItemPosition(int adapterPosition) {
return mItemsList.indexOf(mFilteredItemList.get(adapterPosition));
}
/**
* Update item state. Call this instead of {@link #notifyItemChanged(int) notifyItemChanged()}.
*
* @param position Item position in data set.
*/
public void updateItem(int position) {
int filteredIndex = mFilteredItemList.indexOf(mItemsList.get(position));
if (filteredIndex != -1) {
notifyItemChanged(filteredIndex);
}
}
/**
* Filters data set with filter word.
* For disabling filtering pass empty {@link String String} as argument.
*
* @param filterWord Word to filter with. Can not be null.
*/
public void filter(final String filterWord) {
if (filterWord == null) {
throw new IllegalArgumentException("Filter word can not be null");
}
mFilterWord = filterWord.toLowerCase();
mOffset = 0;
if (!filterSubscription.isUnsubscribed()) {
filterSubscription.unsubscribe();
}
filterSubscription = Observable.defer(new Func0<Observable<List<IT>>>() {
@Override
public Observable<List<IT>> call() {
return Observable.just(getNextFilteredItems());
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<List<IT>>() {
@Override
public void call(List<IT> newItemList) {
animateList(newItemList);
filterSubscription = Subscriptions.empty();
mFilteredItemList = newItemList;
if (mFilterListener != null) {
mFilterListener.onFilterDone();
}
}
});
}
/**
* Returns value that will be used to filter item list.
*
* @param item List item to query
* @return value that will be used to filter item list
*/
protected abstract String getValueForFiltering(IT item);
public void loadNextItems() {
int previousSize = mFilteredItemList.size();
mFilteredItemList.addAll(getNextFilteredItems());
int newSize = mFilteredItemList.size();
notifyItemRangeInserted(previousSize, newSize - previousSize);
}
private List<IT> getNextFilteredItems() {
List<IT> additionalList = new ArrayList<>();
int addedItem = 0;
while (addedItem < LIMIT && mOffset < mItemsList.size()) {
IT item = mItemsList.get(mOffset);
String valueForFiltering = getValueForFiltering(item).toLowerCase();
boolean valueContainsFilterWord = valueForFiltering.contains(mFilterWord);
if (valueContainsFilterWord) {
additionalList.add(item);
addedItem++;
}
mOffset++;
}
return additionalList;
}
private void animateList(List<IT> newFilterList) {
List<Integer> unRemovalList = new ArrayList<>();
List<Integer> addList = new ArrayList<>();
for (int i = 0; i < newFilterList.size(); i++) {
IT item = newFilterList.get(i);
int indexInPrevList = mFilteredItemList.indexOf(item);
if (indexInPrevList == -1) {
addList.add(i);
} else {
unRemovalList.add(indexInPrevList);
}
}
int removedCount = 0;
for (int i = 0; i < mFilteredItemList.size(); i++) {
if (!unRemovalList.contains(i)) {
notifyItemRemoved(i - removedCount);
removedCount++;
}
}
for (Integer index : addList) {
notifyItemInserted(index);
}
}
public interface FilterListener {
void onFilterDone();
}
}