mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-13 19:29:06 +01:00
326 lines
8.2 KiB
Java
326 lines
8.2 KiB
Java
package org.briarproject.android.threaded;
|
|
|
|
import android.animation.ValueAnimator;
|
|
import android.support.annotation.Nullable;
|
|
import android.support.annotation.UiThread;
|
|
import android.support.v7.widget.LinearLayoutManager;
|
|
import android.support.v7.widget.RecyclerView;
|
|
import android.view.LayoutInflater;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
|
|
import org.briarproject.R;
|
|
import org.briarproject.android.util.VersionedAdapter;
|
|
import org.briarproject.api.sync.MessageId;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
import static android.support.v7.widget.RecyclerView.NO_POSITION;
|
|
|
|
public class ThreadItemAdapter<I extends ThreadItem>
|
|
extends RecyclerView.Adapter<BaseThreadItemViewHolder<I>>
|
|
implements VersionedAdapter {
|
|
|
|
static final int UNDEFINED = -1;
|
|
|
|
private final NestedTreeList<I> items = new NestedTreeList<>();
|
|
private final Map<I, ValueAnimator> animatingItems = new HashMap<>();
|
|
private final ThreadItemListener<I> listener;
|
|
private final LinearLayoutManager layoutManager;
|
|
|
|
// highlight not dependant on time
|
|
private I replyItem;
|
|
// temporary highlight
|
|
private I addedItem;
|
|
|
|
private volatile int revision = 0;
|
|
|
|
public ThreadItemAdapter(ThreadItemListener<I> listener,
|
|
LinearLayoutManager layoutManager) {
|
|
this.listener = listener;
|
|
this.layoutManager = layoutManager;
|
|
}
|
|
|
|
@Override
|
|
public BaseThreadItemViewHolder<I> onCreateViewHolder(
|
|
ViewGroup parent, int viewType) {
|
|
View v = LayoutInflater.from(parent.getContext())
|
|
.inflate(R.layout.list_item_thread, parent, false);
|
|
return new ThreadPostViewHolder<>(v);
|
|
}
|
|
|
|
@Override
|
|
public void onBindViewHolder(BaseThreadItemViewHolder<I> ui, int position) {
|
|
I item = getVisibleItem(position);
|
|
if (item == null) return;
|
|
listener.onItemVisible(item);
|
|
ui.bind(this, listener, item, position);
|
|
}
|
|
|
|
@Override
|
|
public int getItemCount() {
|
|
return getVisiblePos(null);
|
|
}
|
|
|
|
I getReplyItem() {
|
|
return replyItem;
|
|
}
|
|
|
|
public void setItems(Collection<I> items) {
|
|
this.items.clear();
|
|
this.items.addAll(items);
|
|
notifyDataSetChanged();
|
|
}
|
|
|
|
public void add(I item) {
|
|
items.add(item);
|
|
addedItem = item;
|
|
if (item.getParentId() == null) {
|
|
notifyItemInserted(getVisiblePos(item));
|
|
} else {
|
|
// Try to find the item's parent and perform the proper ui update if
|
|
// it's present and visible.
|
|
for (int i = items.indexOf(item) - 1; i >= 0; i--) {
|
|
I higherItem = items.get(i);
|
|
if (higherItem.getLevel() < item.getLevel()) {
|
|
// parent found
|
|
if (higherItem.isShowingDescendants()) {
|
|
int parentVisiblePos = getVisiblePos(higherItem);
|
|
if (parentVisiblePos != NO_POSITION) {
|
|
// parent is visible, we need to update its ui
|
|
notifyItemChanged(parentVisiblePos);
|
|
// new item insert ui
|
|
int visiblePos = getVisiblePos(item);
|
|
notifyItemInserted(visiblePos);
|
|
break;
|
|
}
|
|
} else {
|
|
// do not show the new item if its parent is not showing
|
|
// descendants (this can be overridden by the user by
|
|
// pressing the snack bar)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void scrollTo(I item) {
|
|
int visiblePos = getVisiblePos(item);
|
|
if (visiblePos == NO_POSITION && item.getParentId() != null) {
|
|
// The item is not visible due to being hidden by its parent item.
|
|
// Find the parent and make it visible and traverse up the parent
|
|
// chain if necessary to make the item visible
|
|
MessageId parentId = item.getParentId();
|
|
for (int i = items.indexOf(item) - 1; i >= 0; i--) {
|
|
I higherItem = items.get(i);
|
|
if (higherItem.getId().equals(parentId)) {
|
|
// parent found
|
|
showDescendants(higherItem);
|
|
int parentPos = getVisiblePos(higherItem);
|
|
if (parentPos != NO_POSITION) {
|
|
// parent or ancestor is visible, item's visibility
|
|
// is ensured
|
|
notifyItemChanged(parentPos);
|
|
visiblePos = parentPos;
|
|
break;
|
|
}
|
|
// parent or ancestor is hidden, we need to continue up the
|
|
// dependency chain
|
|
parentId = higherItem.getParentId();
|
|
}
|
|
}
|
|
}
|
|
if (visiblePos != NO_POSITION)
|
|
layoutManager.scrollToPositionWithOffset(visiblePos, 0);
|
|
}
|
|
|
|
int getReplyCount(I item) {
|
|
int counter = 0;
|
|
int pos = items.indexOf(item);
|
|
if (pos >= 0) {
|
|
int ancestorLvl = item.getLevel();
|
|
for (int i = pos + 1; i < items.size(); i++) {
|
|
int descendantLvl = items.get(i).getLevel();
|
|
if (descendantLvl <= ancestorLvl)
|
|
break;
|
|
if (descendantLvl == ancestorLvl + 1)
|
|
counter++;
|
|
}
|
|
}
|
|
return counter;
|
|
}
|
|
|
|
void setReplyItem(@Nullable I item) {
|
|
if (replyItem != null) {
|
|
notifyItemChanged(getVisiblePos(replyItem));
|
|
}
|
|
replyItem = item;
|
|
if (replyItem != null) {
|
|
notifyItemChanged(getVisiblePos(replyItem));
|
|
}
|
|
}
|
|
|
|
void setReplyItemById(MessageId id) {
|
|
for (I item : items) {
|
|
if (item.getId().equals(id)) {
|
|
setReplyItem(item);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private List<Integer> getSubTreeIndexes(int pos, int levelLimit) {
|
|
List<Integer> indexList = new ArrayList<>();
|
|
|
|
for (int i = pos + 1; i < getItemCount(); i++) {
|
|
I item = getVisibleItem(i);
|
|
if (item != null && item.getLevel() > levelLimit) {
|
|
indexList.add(i);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
return indexList;
|
|
}
|
|
|
|
public void showDescendants(I item) {
|
|
item.setShowingDescendants(true);
|
|
int visiblePos = getVisiblePos(item);
|
|
List<Integer> indexList =
|
|
getSubTreeIndexes(visiblePos, item.getLevel());
|
|
if (!indexList.isEmpty()) {
|
|
if (indexList.size() == 1) {
|
|
notifyItemInserted(indexList.get(0));
|
|
} else {
|
|
notifyItemRangeInserted(indexList.get(0),
|
|
indexList.size());
|
|
}
|
|
}
|
|
}
|
|
|
|
public void hideDescendants(I item) {
|
|
int visiblePos = getVisiblePos(item);
|
|
List<Integer> indexList =
|
|
getSubTreeIndexes(visiblePos, item.getLevel());
|
|
if (!indexList.isEmpty()) {
|
|
// stop animating children
|
|
for (int index : indexList) {
|
|
ValueAnimator anim = animatingItems.get(items.get(index));
|
|
if (anim != null && anim.isRunning()) {
|
|
anim.cancel();
|
|
}
|
|
}
|
|
if (indexList.size() == 1) {
|
|
notifyItemRemoved(indexList.get(0));
|
|
} else {
|
|
notifyItemRangeRemoved(indexList.get(0),
|
|
indexList.size());
|
|
}
|
|
}
|
|
item.setShowingDescendants(false);
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the visible item at the given position
|
|
*
|
|
* @param position is visible item index
|
|
* @return the visible item at index 'position' from an ordered list of
|
|
* visible items, or null if 'position' is larger than the number of
|
|
* visible items.
|
|
*/
|
|
@Nullable
|
|
public I getVisibleItem(int position) {
|
|
int levelLimit = UNDEFINED;
|
|
for (I item : items) {
|
|
if (levelLimit >= 0) {
|
|
// skip hidden items that their parent is hiding
|
|
if (item.getLevel() > levelLimit) {
|
|
continue;
|
|
}
|
|
levelLimit = UNDEFINED;
|
|
}
|
|
if (!item.isShowingDescendants()) {
|
|
levelLimit = item.getLevel();
|
|
}
|
|
if (position-- == 0) {
|
|
return item;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
boolean isVisible(I item) {
|
|
return getVisiblePos(item) != NO_POSITION;
|
|
}
|
|
|
|
/**
|
|
* Returns the visible position of the given item.
|
|
*
|
|
* @param item the item to find the visible position of, or null to
|
|
* return the total count of visible items.
|
|
* @return the visible position of 'item', or the total number of visible
|
|
* items if 'item' is null. If 'item' is not visible, NO_POSITION is
|
|
* returned.
|
|
*/
|
|
private int getVisiblePos(@Nullable I item) {
|
|
int visibleCounter = 0;
|
|
int levelLimit = UNDEFINED;
|
|
for (I i : items) {
|
|
if (levelLimit >= 0) {
|
|
if (i.getLevel() > levelLimit) {
|
|
// skip all the items below a non visible branch
|
|
continue;
|
|
}
|
|
levelLimit = UNDEFINED;
|
|
}
|
|
if (item != null && item.equals(i)) {
|
|
return visibleCounter;
|
|
} else if (!i.isShowingDescendants()) {
|
|
levelLimit = i.getLevel();
|
|
}
|
|
visibleCounter++;
|
|
}
|
|
return item == null ? visibleCounter : NO_POSITION;
|
|
}
|
|
|
|
I getAddedItem() {
|
|
return addedItem;
|
|
}
|
|
|
|
void clearAddedItem() {
|
|
addedItem = null;
|
|
}
|
|
|
|
void addAnimatingItem(I item, ValueAnimator anim) {
|
|
animatingItems.put(item, anim);
|
|
}
|
|
|
|
void removeAnimatingItem(I item) {
|
|
animatingItems.remove(item);
|
|
}
|
|
|
|
@Override
|
|
public int getRevision() {
|
|
return revision;
|
|
}
|
|
|
|
@UiThread
|
|
@Override
|
|
public void incrementRevision() {
|
|
revision++;
|
|
}
|
|
|
|
public interface ThreadItemListener<I> {
|
|
|
|
void onItemVisible(I item);
|
|
|
|
void onReplyClick(I item);
|
|
}
|
|
}
|