package com.mikepenz.fastadapter_extensions.utilities;
import android.util.Log;
import com.mikepenz.fastadapter.FastAdapter;
import com.mikepenz.fastadapter.IAdapter;
import com.mikepenz.fastadapter.IExpandable;
import com.mikepenz.fastadapter.IItem;
import com.mikepenz.fastadapter.IItemAdapter;
import com.mikepenz.fastadapter.ISubItem;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
/**
* Created by flisar on 15.09.2016.
*/
public class SubItemUtil {
/**
* returns a set of selected items, regardless of their visibility
*
* @param adapter the adapter instance
* @return a set of all selected items and subitems
*/
public static Set<IItem> getSelectedItems(FastAdapter adapter) {
Set<IItem> selections = new HashSet<>();
int length = adapter.getItemCount();
List<IItem> items = new ArrayList<>();
for (int i = 0; i < length; i++) {
items.add(adapter.getItem(i));
}
updateSelectedItemsWithCollapsed(selections, items);
return selections;
}
private static void updateSelectedItemsWithCollapsed(Set<IItem> selected, List<IItem> items) {
int length = items.size();
for (int i = 0; i < length; i++) {
if (items.get(i).isSelected()) {
selected.add(items.get(i));
}
if (items.get(i) instanceof IExpandable && ((IExpandable) items.get(i)).getSubItems() != null) {
updateSelectedItemsWithCollapsed(selected, ((IExpandable) items.get(i)).getSubItems());
}
}
}
/**
* counts the items in the adapter, respecting subitems regardless of there current visibility
*
* @param adapter the adapter instance
* @param predicate predicate against which each item will be checked before counting it
* @return number of items in the adapter that apply to the predicate
*/
public static int countItems(final IItemAdapter adapter, IPredicate predicate) {
return countItems(adapter.getAdapterItems(), true, false, predicate);
}
/**
* counts the items in the adapter, respecting subitems regardless of there current visibility
*
* @param adapter the adapter instance
* @param countHeaders if true, headers will be counted as well
* @return number of items in the adapter
*/
public static int countItems(final IItemAdapter adapter, boolean countHeaders) {
return countItems(adapter.getAdapterItems(), countHeaders, false, null);
}
private static int countItems(List<IItem> items, boolean countHeaders, boolean subItemsOnly, IPredicate predicate) {
return getAllItems(items, countHeaders, subItemsOnly, predicate).size();
}
/**
* retrieves a flat list of the items in the adapter, respecting subitems regardless of there current visibility
*
* @param adapter the adapter instance
* @param predicate predicate against which each item will be checked before adding it to the result
* @return list of items in the adapter that apply to the predicate
*/
public static List<IItem> getAllItems(final IItemAdapter adapter, IPredicate predicate) {
return getAllItems(adapter.getAdapterItems(), true, false, predicate);
}
/**
* retrieves a flat list of the items in the adapter, respecting subitems regardless of there current visibility
*
* @param adapter the adapter instance
* @param countHeaders if true, headers will be counted as well
* @return list of items in the adapter
*/
public static List<IItem> getAllItems(final IItemAdapter adapter, boolean countHeaders) {
return getAllItems(adapter.getAdapterItems(), countHeaders, false, null);
}
/**
* retrieves a flat list of the items in the provided list, respecting subitems regardless of there current visibility
*
* @param items the list of items to process
* @param countHeaders if true, headers will be counted as well
* @return list of items in the adapter
*/
public static List<IItem> getAllItems(List<IItem> items, boolean countHeaders, IPredicate predicate) {
return getAllItems(items, countHeaders, false, predicate);
}
/**
* internal function!
*
* Why countHeaders and subItems => because the subItemsOnly is an internal flag for the recursive call to optimise it!
*/
private static List<IItem> getAllItems(List<IItem> items, boolean countHeaders, boolean subItemsOnly, IPredicate predicate) {
List<IItem> res = new ArrayList<>();
if (items == null || items.size() == 0) {
return res;
}
int temp;
int itemCount = items.size();
IItem item;
List<IItem> subItems;
for (int i = 0; i < itemCount; i++) {
item = items.get(i);
if (item instanceof IExpandable && ((IExpandable) item).getSubItems() != null) {
subItems = ((IExpandable) item).getSubItems();
if (predicate == null) {
if (countHeaders) {
res.add(item);
}
if (subItems != null && subItems.size() > 0) {
res.addAll(subItems);
}
res.addAll(getAllItems(subItems, countHeaders, true, predicate));
} else {
if (countHeaders && predicate.apply(item)) {
res.add(item);
}
temp = subItems != null ? subItems.size() : 0;
for (int j = 0; j < temp; j++) {
if (predicate.apply(subItems.get(j))) {
res.add(subItems.get(j));
}
}
}
}
// in some cases, we must manually check, if the item is a sub item, process is optimised as much as possible via the subItemsOnly parameter already
// sub items will be counted in above if statement!
else if (!subItemsOnly && getParent(item) == null) {
if (predicate == null) {
res.add(item);
} else if (predicate.apply(item)) {
res.add(item);
}
}
}
return res;
}
/**
* counts the selected items in the adapter underneath an expandable item, recursively
*
* @param adapter the adapter instance
* @param header the header who's selected children should be counted
* @return number of selected items underneath the header
*/
public static <T extends IItem & IExpandable> int countSelectedSubItems(final FastAdapter adapter, T header) {
Set<IItem> selections = getSelectedItems(adapter);
return countSelectedSubItems(selections, header);
}
public static <T extends IItem & IExpandable> int countSelectedSubItems(Set<IItem> selections, T header) {
int count = 0;
List<IItem> subItems = header.getSubItems();
int items = header.getSubItems() != null ? header.getSubItems().size() : 0;
for (int i = 0; i < items; i++) {
if (selections.contains(subItems.get(i))) {
count++;
}
if (subItems.get(i) instanceof IExpandable && ((IExpandable) subItems.get(i)).getSubItems() != null) {
count += countSelectedSubItems(selections, (T) subItems.get(i));
}
}
return count;
}
/**
* select or unselect all sub itmes underneath an expandable item
*
* @param adapter the adapter instance
* @param header the header who's children should be selected or deselected
* @param select the new selected state of the sub items
*/
public static <T extends IItem & IExpandable> void selectAllSubItems(final FastAdapter adapter, T header, boolean select) {
selectAllSubItems(adapter, header, select, false, null);
}
/**
* select or unselect all sub itmes underneath an expandable item
*
* @param adapter the adapter instance
* @param header the header who's children should be selected or deselected
* @param select the new selected state of the sub items
* @param notifyParent true, if the parent should be notified about the changes of its children selection state
* @param payload payload for the notifying function
*/
public static <T extends IItem & IExpandable> void selectAllSubItems(final FastAdapter adapter, T header, boolean select, boolean notifyParent, Object payload) {
int subItems = header.getSubItems().size();
int position = adapter.getPosition(header);
if (header.isExpanded()) {
for (int i = 0; i < subItems; i++) {
if (((IItem)header.getSubItems().get(i)).isSelectable()) {
if (select) {
adapter.select(position + i + 1);
} else {
adapter.deselect(position + i + 1);
}
}
if (header.getSubItems().get(i) instanceof IExpandable)
selectAllSubItems(adapter, header, select, notifyParent, payload);
}
} else {
for (int i = 0; i < subItems; i++) {
if (((IItem)header.getSubItems().get(i)).isSelectable()) {
((IItem) header.getSubItems().get(i)).withSetSelected(select);
}
if (header.getSubItems().get(i) instanceof IExpandable)
selectAllSubItems(adapter, header, select, notifyParent, payload);
}
}
// we must notify the view only!
if (notifyParent && position >= 0) {
adapter.notifyItemChanged(position, payload);
}
}
private static <T extends IExpandable & IItem> T getParent(IItem item) {
if (item instanceof ISubItem) {
return (T) ((ISubItem) item).getParent();
}
return null;
}
/**
* deletes all selected items from the adapter respecting if the are sub items or not
* subitems are removed from their parents sublists, main items are directly removed
*
* @param deleteEmptyHeaders if true, empty headers will be removed from the adapter
* @return List of items that have been removed from the adapter
*/
public static List<IItem> deleteSelected(final FastAdapter fastAdapter, boolean notifyParent, boolean deleteEmptyHeaders) {
List<IItem> deleted = new ArrayList<>();
// we use a LinkedList, because this has performance advantages when modifying the listIterator during iteration!
// Modifying list is O(1)
LinkedList<IItem> selectedItems = new LinkedList<>(getSelectedItems(fastAdapter));
// Log.d("DELETE", "selectedItems: " + selectedItems.size());
// we delete item per item from the adapter directly or from the parent
// if keepEmptyHeaders is false, we add empty headers to the selected items set via the iterator, so that they are processed in the loop as well
IItem item, parent;
int pos, parentPos;
boolean expanded;
ListIterator<IItem> it = selectedItems.listIterator();
while (it.hasNext()) {
item = it.next();
pos = fastAdapter.getPosition(item);
// search for parent - if we find one, we remove the item from the parent's subitems directly
parent = getParent(item);
if (parent != null) {
parentPos = fastAdapter.getPosition(parent);
boolean success = ((IExpandable) parent).getSubItems().remove(item);
// Log.d("DELETE", "success=" + success + " | deletedId=" + item.getIdentifier() + " | parentId=" + parent.getIdentifier() + " (sub items: " + ((IExpandable) parent).getSubItems().size() + ") | parentPos=" + parentPos);
// check if parent is expanded and notify the adapter about the removed item, if necessary (only if parent is visible)
if (parentPos != -1 && ((IExpandable) parent).isExpanded()) {
fastAdapter.notifyAdapterSubItemsChanged(parentPos, ((IExpandable) parent).getSubItems().size() + 1);
}
// if desired, notify the parent about its changed items (only if parent is visible!)
if (parentPos != -1 && notifyParent) {
expanded = ((IExpandable) parent).isExpanded();
fastAdapter.notifyAdapterItemChanged(parentPos);
// expand the item again if it was expanded before calling notifyAdapterItemChanged
if (expanded) {
fastAdapter.expand(parentPos);
}
}
deleted.add(item);
if (deleteEmptyHeaders && ((IExpandable) parent).getSubItems().size() == 0) {
it.add(parent);
it.previous();
}
} else if (pos != -1) {
// if we did not find a parent, we remove the item from the adapter
IAdapter adapter = fastAdapter.getAdapter(pos);
boolean success = false;
if (adapter instanceof IItemAdapter) {
success = ((IItemAdapter) adapter).remove(pos) != null;
}
boolean isHeader = item instanceof IExpandable && ((IExpandable) item).getSubItems() != null;
// Log.d("DELETE", "success=" + success + " | deletedId=" + item.getIdentifier() + "(" + (isHeader ? "EMPTY HEADER" : "ITEM WITHOUT HEADER") + ")");
deleted.add(item);
}
}
// Log.d("DELETE", "deleted (incl. empty headers): " + deleted.size());
return deleted;
}
/**
* deletes all items in identifiersToDelete collection from the adapter respecting if there are sub items or not
* subitems are removed from their parents sublists, main items are directly removed
*
* @param fastAdapter the adapter to remove the items from
* @param identifiersToDelete ids of items to remove
* @param notifyParent if true, headers of removed items will be notified about the change of their child items
* @param deleteEmptyHeaders if true, empty headers will be removed from the adapter
* @return List of items that have been removed from the adapter
*/
public static List<IItem> delete(final FastAdapter fastAdapter, Collection<Long> identifiersToDelete, boolean notifyParent, boolean deleteEmptyHeaders) {
List<IItem> deleted = new ArrayList<>();
if (identifiersToDelete == null || identifiersToDelete.size() == 0) {
return deleted;
}
// we use a LinkedList, because this has performance advantages when modifying the listIterator during iteration!
// Modifying list is O(1)
LinkedList<Long> identifiers = new LinkedList<>(identifiersToDelete);
// we delete item per item from the adapter directly or from the parent
// if keepEmptyHeaders is false, we add empty headers to the selected items set via the iterator, so that they are processed in the loop as well
IItem item, parent;
int pos, parentPos;
boolean expanded;
Long identifier;
ListIterator<Long> it = identifiers.listIterator();
while (it.hasNext()) {
identifier = it.next();
pos = fastAdapter.getPosition(identifier);
item = fastAdapter.getItem(pos);
// search for parent - if we find one, we remove the item from the parent's subitems directly
parent = getParent(item);
if (parent != null) {
parentPos = fastAdapter.getPosition(parent);
boolean success = ((IExpandable) parent).getSubItems().remove(item);
// Log.d("DELETE", "success=" + success + " | deletedId=" + item.getIdentifier() + " | parentId=" + parent.getIdentifier() + " (sub items: " + ((IExpandable) parent).getSubItems().size() + ") | parentPos=" + parentPos);
// check if parent is expanded and notify the adapter about the removed item, if necessary (only if parent is visible)
if (parentPos != -1 && ((IExpandable) parent).isExpanded()) {
fastAdapter.notifyAdapterSubItemsChanged(parentPos, ((IExpandable) parent).getSubItems().size() + 1);
}
// if desired, notify the parent about it's changed items (only if parent is visible!)
if (parentPos != -1 && notifyParent) {
expanded = ((IExpandable) parent).isExpanded();
fastAdapter.notifyAdapterItemChanged(parentPos);
// expand the item again if it was expanded before calling notifyAdapterItemChanged
if (expanded) {
fastAdapter.expand(parentPos);
}
}
deleted.add(item);
if (deleteEmptyHeaders && ((IExpandable) parent).getSubItems().size() == 0) {
it.add(parent.getIdentifier());
it.previous();
}
} else if (pos != -1) {
// if we did not find a parent, we remove the item from the adapter
IAdapter adapter = fastAdapter.getAdapter(pos);
boolean success = false;
if (adapter instanceof IItemAdapter) {
success = ((IItemAdapter) adapter).remove(pos) != null;
if (success) {
fastAdapter.notifyAdapterItemRemoved(pos);
}
}
boolean isHeader = item instanceof IExpandable && ((IExpandable) item).getSubItems() != null;
// Log.d("DELETE", "success=" + success + " | deletedId=" + item.getIdentifier() + "(" + (isHeader ? "EMPTY HEADER" : "ITEM WITHOUT HEADER") + ")");
deleted.add(item);
}
}
// Log.d("DELETE", "deleted (incl. empty headers): " + deleted.size());
return deleted;
}
/**
* notifies items (incl. sub items if they are currently extended)
*
* @param adapter the adapter
* @param identifiers set of identifiers that should be notified
*/
public static <Item extends IItem & IExpandable> void notifyItemsChanged(final FastAdapter adapter, Set<Long> identifiers) {
notifyItemsChanged(adapter, identifiers, false);
}
/**
* notifies items (incl. sub items if they are currently extended)
*
* @param adapter the adapter
* @param identifiers set of identifiers that should be notified
* @param restoreExpandedState true, if expanded headers should stay expanded
*/
public static <Item extends IItem & IExpandable> void notifyItemsChanged(final FastAdapter adapter, Set<Long> identifiers, boolean restoreExpandedState) {
int i;
IItem item;
for (i = 0; i < adapter.getItemCount(); i++) {
item = adapter.getItem(i);
if (item instanceof IExpandable) {
notifyItemsChanged(adapter, (Item) item, identifiers, true, restoreExpandedState);
} else if (identifiers.contains(item.getIdentifier())) {
adapter.notifyAdapterItemChanged(i);
}
}
}
/**
* notifies items (incl. sub items if they are currently extended)
*
* @param adapter the adapter
* @param header the expandable header that should be checked (incl. sub items)
* @param identifiers set of identifiers that should be notified
* @param checkSubItems true, if sub items of headers items should be checked recursively
* @param restoreExpandedState true, if expanded headers should stay expanded
*/
public static <Item extends IItem & IExpandable> void notifyItemsChanged(final FastAdapter adapter, Item header, Set<Long> identifiers, boolean checkSubItems, boolean restoreExpandedState) {
int subItems = header.getSubItems().size();
int position = adapter.getPosition(header);
boolean expanded = header.isExpanded();
// 1) check header itself
if (identifiers.contains(header.getIdentifier())) {
adapter.notifyAdapterItemChanged(position);
}
// 2) check sub items, recursively
IItem item;
if (header.isExpanded()) {
for (int i = 0; i < subItems; i++) {
item = (IItem)header.getSubItems().get(i);
if (identifiers.contains(item.getIdentifier())) {
// Log.d("NOTIFY", "Position=" + position + ", i=" + i);
adapter.notifyAdapterItemChanged(position + i + 1);
}
if (checkSubItems && item instanceof IExpandable) {
notifyItemsChanged(adapter, (Item)item, identifiers, true, restoreExpandedState);
}
}
}
if (restoreExpandedState && expanded) {
adapter.expand(position);
}
}
public interface IPredicate<T> {
boolean apply(T data);
}
}