mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-13 11:19:04 +01:00
Refactor Forum Activity and adapters to be re-used for private groups
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
package org.briarproject.android.threaded;
|
||||
|
||||
import android.support.annotation.UiThread;
|
||||
|
||||
import org.briarproject.api.clients.MessageTree;
|
||||
import org.briarproject.clients.MessageTreeImpl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
@UiThread
|
||||
public class NestedTreeList<T extends MessageTree.MessageNode>
|
||||
implements Iterable<T> {
|
||||
|
||||
private final MessageTree<T> tree = new MessageTreeImpl<>();
|
||||
private List<T> depthFirstCollection = new ArrayList<>();
|
||||
|
||||
public void addAll(Collection<T> collection) {
|
||||
tree.add(collection);
|
||||
depthFirstCollection = new ArrayList<>(tree.depthFirstOrder());
|
||||
}
|
||||
|
||||
public void add(T elem) {
|
||||
tree.add(elem);
|
||||
depthFirstCollection = new ArrayList<>(tree.depthFirstOrder());
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
tree.clear();
|
||||
depthFirstCollection.clear();
|
||||
}
|
||||
|
||||
public T get(int index) {
|
||||
return depthFirstCollection.get(index);
|
||||
}
|
||||
|
||||
public int indexOf(T elem) {
|
||||
return depthFirstCollection.indexOf(elem);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return depthFirstCollection.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return depthFirstCollection.iterator();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package org.briarproject.android.threaded;
|
||||
|
||||
import org.briarproject.api.clients.MessageTree.MessageNode;
|
||||
import org.briarproject.api.identity.Author;
|
||||
import org.briarproject.api.identity.Author.Status;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
import static org.briarproject.android.threaded.ThreadItemAdapter.UNDEFINED;
|
||||
|
||||
/* This class is not thread safe */
|
||||
public abstract class ThreadItem implements MessageNode {
|
||||
|
||||
private final MessageId messageId;
|
||||
private final MessageId parentId;
|
||||
private final String text;
|
||||
private final long timestamp;
|
||||
private final Author author;
|
||||
private final Status status;
|
||||
private int level = UNDEFINED;
|
||||
private boolean isShowingDescendants = true;
|
||||
private int descendantCount = 0;
|
||||
private boolean isRead;
|
||||
|
||||
public ThreadItem(MessageId messageId, MessageId parentId, String text,
|
||||
long timestamp, Author author, Status status, boolean isRead) {
|
||||
this.messageId = messageId;
|
||||
this.parentId = parentId;
|
||||
this.text = text;
|
||||
this.timestamp = timestamp;
|
||||
this.author = author;
|
||||
this.status = status;
|
||||
this.isRead = isRead;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public int getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageId getId() {
|
||||
return messageId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageId getParentId() {
|
||||
return parentId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public Author getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public boolean isShowingDescendants() {
|
||||
return isShowingDescendants;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLevel(int level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
public void setShowingDescendants(boolean showingDescendants) {
|
||||
this.isShowingDescendants = showingDescendants;
|
||||
}
|
||||
|
||||
public boolean isRead() {
|
||||
return isRead;
|
||||
}
|
||||
|
||||
public void setRead(boolean read) {
|
||||
isRead = read;
|
||||
}
|
||||
|
||||
public boolean hasDescendants() {
|
||||
return descendantCount > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDescendantCount(int descendantCount) {
|
||||
this.descendantCount = descendantCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
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 org.briarproject.api.sync.MessageId;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static android.support.v7.widget.RecyclerView.NO_POSITION;
|
||||
|
||||
@UiThread
|
||||
public abstract class ThreadItemAdapter<I extends ThreadItem>
|
||||
extends RecyclerView.Adapter<ThreadItemViewHolder<I>> {
|
||||
|
||||
static final int UNDEFINED = -1;
|
||||
|
||||
private final NestedTreeList<I> items =
|
||||
new NestedTreeList<>();
|
||||
private final Map<I, ValueAnimator> animatingItems =
|
||||
new HashMap<>();
|
||||
// highlight not dependant on time
|
||||
private I replyItem;
|
||||
// temporary highlight
|
||||
private I addedEntry;
|
||||
|
||||
private final ThreadItemListener<I> listener;
|
||||
private final LinearLayoutManager layoutManager;
|
||||
|
||||
public ThreadItemAdapter(ThreadItemListener<I> listener,
|
||||
LinearLayoutManager layoutManager) {
|
||||
this.listener = listener;
|
||||
this.layoutManager = layoutManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(ThreadItemViewHolder<I> ui, int position) {
|
||||
final I item = getVisibleItem(position);
|
||||
if (item == null) return;
|
||||
listener.onItemVisible(item);
|
||||
ui.bind(this, listener, item, position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return getVisiblePos(null);
|
||||
}
|
||||
|
||||
public I getReplyItem() {
|
||||
return replyItem;
|
||||
}
|
||||
|
||||
public void setItems(List<I> items) {
|
||||
this.items.clear();
|
||||
this.items.addAll(items);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void add(I item) {
|
||||
items.add(item);
|
||||
addedEntry = 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public 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, entry'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;
|
||||
}
|
||||
|
||||
public void setReplyItem(@Nullable I entry) {
|
||||
if (replyItem != null) {
|
||||
notifyItemChanged(getVisiblePos(replyItem));
|
||||
}
|
||||
replyItem = entry;
|
||||
if (replyItem != null) {
|
||||
notifyItemChanged(getVisiblePos(replyItem));
|
||||
}
|
||||
}
|
||||
|
||||
public void setReplyItemById(byte[] id) {
|
||||
MessageId messageId = new MessageId(id);
|
||||
for (I item : items) {
|
||||
if (item.getId().equals(messageId)) {
|
||||
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 entry index
|
||||
* @return the visible entry at index position from an ordered list of
|
||||
* visible entries, or null if position is larger than
|
||||
* the number of visible entries.
|
||||
*/
|
||||
@Nullable
|
||||
public I getVisibleItem(int position) {
|
||||
int levelLimit = UNDEFINED;
|
||||
for (I item : items) {
|
||||
if (levelLimit >= 0) {
|
||||
// skip hidden entries 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;
|
||||
}
|
||||
|
||||
public boolean isVisible(I item) {
|
||||
return getVisiblePos(item) != NO_POSITION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the visible position of the given ThreadItem
|
||||
* @param item the ThreadItem to find the visible position of, or null to
|
||||
* return the total count of visible elements
|
||||
* @return the visible position of item, or the total number of visible
|
||||
* elements if sEntry 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 iItem : items) {
|
||||
if (levelLimit >= 0) {
|
||||
if (iItem.getLevel() > levelLimit) {
|
||||
// skip all the entries below a non visible branch
|
||||
continue;
|
||||
}
|
||||
levelLimit = UNDEFINED;
|
||||
}
|
||||
if (item != null && item.equals(iItem)) {
|
||||
return visibleCounter;
|
||||
} else if (!iItem.isShowingDescendants()) {
|
||||
levelLimit = iItem.getLevel();
|
||||
}
|
||||
visibleCounter++;
|
||||
}
|
||||
return item == null ? visibleCounter : NO_POSITION;
|
||||
}
|
||||
|
||||
I getAddedItem() {
|
||||
return addedEntry;
|
||||
}
|
||||
|
||||
void clearAddedItem() {
|
||||
addedEntry = null;
|
||||
}
|
||||
|
||||
void addAnimatingItem(I item, ValueAnimator anim) {
|
||||
animatingItems.put(item, anim);
|
||||
}
|
||||
|
||||
void removeAnimatingItem(I item) {
|
||||
animatingItems.remove(item);
|
||||
}
|
||||
|
||||
public interface ThreadItemListener<I> {
|
||||
void onItemVisible(I item);
|
||||
|
||||
void onReplyClick(I item);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package org.briarproject.android.threaded;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.ArgbEvaluator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.threaded.ThreadItemAdapter.ThreadItemListener;
|
||||
import org.briarproject.android.view.AuthorView;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
|
||||
@UiThread
|
||||
public abstract class ThreadItemViewHolder<I extends ThreadItem>
|
||||
extends RecyclerView.ViewHolder {
|
||||
|
||||
private final static int ANIMATION_DURATION = 5000;
|
||||
|
||||
private final TextView textView, lvlText, repliesText;
|
||||
private final AuthorView author;
|
||||
private final View[] lvls;
|
||||
private final View chevron, replyButton;
|
||||
private final ViewGroup cell;
|
||||
private final View topDivider;
|
||||
|
||||
public ThreadItemViewHolder(View v) {
|
||||
super(v);
|
||||
|
||||
textView = (TextView) v.findViewById(R.id.text);
|
||||
lvlText = (TextView) v.findViewById(R.id.nested_line_text);
|
||||
author = (AuthorView) v.findViewById(R.id.author);
|
||||
repliesText = (TextView) v.findViewById(R.id.replies);
|
||||
int[] nestedLineIds = {
|
||||
R.id.nested_line_1, R.id.nested_line_2, R.id.nested_line_3,
|
||||
R.id.nested_line_4, R.id.nested_line_5
|
||||
};
|
||||
lvls = new View[nestedLineIds.length];
|
||||
for (int i = 0; i < lvls.length; i++) {
|
||||
lvls[i] = v.findViewById(nestedLineIds[i]);
|
||||
}
|
||||
chevron = v.findViewById(R.id.chevron);
|
||||
replyButton = v.findViewById(R.id.btn_reply);
|
||||
cell = (ViewGroup) v.findViewById(R.id.forum_cell);
|
||||
topDivider = v.findViewById(R.id.top_divider);
|
||||
}
|
||||
|
||||
// TODO improve encapsulation, so we don't need to pass the adapter here
|
||||
public void bind(final ThreadItemAdapter<I> adapter,
|
||||
final ThreadItemListener listener, final I item, int pos) {
|
||||
|
||||
textView.setText(StringUtils.trim(item.getText()));
|
||||
|
||||
if (pos == 0) {
|
||||
topDivider.setVisibility(View.INVISIBLE);
|
||||
} else {
|
||||
topDivider.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
for (int i = 0; i < lvls.length; i++) {
|
||||
lvls[i].setVisibility(i < item.getLevel() ? VISIBLE : GONE);
|
||||
}
|
||||
if (item.getLevel() > 5) {
|
||||
lvlText.setVisibility(VISIBLE);
|
||||
lvlText.setText("" + item.getLevel());
|
||||
} else {
|
||||
lvlText.setVisibility(GONE);
|
||||
}
|
||||
author.setAuthor(item.getAuthor());
|
||||
author.setDate(item.getTimestamp());
|
||||
author.setAuthorStatus(item.getStatus());
|
||||
|
||||
int replies = adapter.getReplyCount(item);
|
||||
if (replies == 0) {
|
||||
repliesText.setText("");
|
||||
} else {
|
||||
repliesText.setText(getContext().getResources()
|
||||
.getQuantityString(R.plurals.message_replies, replies,
|
||||
replies));
|
||||
}
|
||||
|
||||
if (item.hasDescendants()) {
|
||||
chevron.setVisibility(VISIBLE);
|
||||
if (item.isShowingDescendants()) {
|
||||
chevron.setSelected(false);
|
||||
} else {
|
||||
chevron.setSelected(true);
|
||||
}
|
||||
chevron.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
chevron.setSelected(!chevron.isSelected());
|
||||
if (chevron.isSelected()) {
|
||||
adapter.hideDescendants(item);
|
||||
} else {
|
||||
adapter.showDescendants(item);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
chevron.setVisibility(INVISIBLE);
|
||||
}
|
||||
if (item.equals(adapter.getReplyItem())) {
|
||||
cell.setBackgroundColor(ContextCompat
|
||||
.getColor(getContext(), R.color.forum_cell_highlight));
|
||||
} else if (item.equals(adapter.getAddedItem())) {
|
||||
cell.setBackgroundColor(ContextCompat
|
||||
.getColor(getContext(), R.color.forum_cell_highlight));
|
||||
animateFadeOut(adapter, adapter.getAddedItem());
|
||||
adapter.clearAddedItem();
|
||||
} else {
|
||||
cell.setBackgroundColor(ContextCompat
|
||||
.getColor(getContext(), R.color.window_background));
|
||||
}
|
||||
replyButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onReplyClick(item);
|
||||
adapter.scrollTo(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void animateFadeOut(final ThreadItemAdapter<I> adapter,
|
||||
final I addedItem) {
|
||||
|
||||
setIsRecyclable(false);
|
||||
ValueAnimator anim = new ValueAnimator();
|
||||
adapter.addAnimatingItem(addedItem, anim);
|
||||
ColorDrawable viewColor = (ColorDrawable) cell.getBackground();
|
||||
anim.setIntValues(viewColor.getColor(), ContextCompat
|
||||
.getColor(getContext(), R.color.window_background));
|
||||
anim.setEvaluator(new ArgbEvaluator());
|
||||
anim.addListener(new Animator.AnimatorListener() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
setIsRecyclable(true);
|
||||
adapter.removeAnimatingItem(addedItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
setIsRecyclable(true);
|
||||
adapter.removeAnimatingItem(addedItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animator animation) {
|
||||
|
||||
}
|
||||
});
|
||||
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator valueAnimator) {
|
||||
cell.setBackgroundColor(
|
||||
(Integer) valueAnimator.getAnimatedValue());
|
||||
}
|
||||
});
|
||||
anim.setDuration(ANIMATION_DURATION);
|
||||
anim.start();
|
||||
}
|
||||
|
||||
private Context getContext() {
|
||||
return textView.getContext();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
package org.briarproject.android.threaded;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.CallSuper;
|
||||
import android.support.annotation.LayoutRes;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.BriarActivity;
|
||||
import org.briarproject.android.api.AndroidNotificationManager;
|
||||
import org.briarproject.android.threaded.ThreadItemAdapter.ThreadItemListener;
|
||||
import org.briarproject.android.view.BriarRecyclerView;
|
||||
import org.briarproject.android.view.TextInputView;
|
||||
import org.briarproject.android.view.TextInputView.TextInputListener;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.support.design.widget.Snackbar.make;
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
|
||||
public abstract class ThreadListActivity<I extends ThreadItem, A extends ThreadItemAdapter<I>>
|
||||
extends BriarActivity
|
||||
implements TextInputListener, ThreadItemListener<I> {
|
||||
|
||||
protected static final String KEY_INPUT_VISIBILITY = "inputVisibility";
|
||||
protected static final String KEY_REPLY_ID = "replyId";
|
||||
|
||||
@Inject
|
||||
protected AndroidNotificationManager notificationManager;
|
||||
|
||||
protected A adapter;
|
||||
protected BriarRecyclerView list;
|
||||
protected TextInputView textInput;
|
||||
protected GroupId groupId;
|
||||
|
||||
@CallSuper
|
||||
@Override
|
||||
public void onCreate(final Bundle state) {
|
||||
super.onCreate(state);
|
||||
|
||||
setContentView(getLayout());
|
||||
|
||||
Intent i = getIntent();
|
||||
byte[] b = i.getByteArrayExtra(GROUP_ID);
|
||||
if (b == null) throw new IllegalStateException("No GroupId in intent.");
|
||||
groupId = new GroupId(b);
|
||||
|
||||
textInput = (TextInputView) findViewById(R.id.text_input_container);
|
||||
textInput.setVisibility(GONE);
|
||||
textInput.setListener(this);
|
||||
list = (BriarRecyclerView) findViewById(R.id.list);
|
||||
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
|
||||
list.setLayoutManager(linearLayoutManager);
|
||||
adapter = createAdapter(linearLayoutManager);
|
||||
list.setAdapter(adapter);
|
||||
}
|
||||
|
||||
protected abstract @LayoutRes int getLayout();
|
||||
|
||||
protected abstract A createAdapter(LinearLayoutManager layoutManager);
|
||||
|
||||
@CallSuper
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
notificationManager.blockNotification(groupId);
|
||||
list.startPeriodicUpdate();
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
notificationManager.unblockNotification(groupId);
|
||||
list.stopPeriodicUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
textInput.setVisibility(
|
||||
savedInstanceState.getBoolean(KEY_INPUT_VISIBILITY) ?
|
||||
VISIBLE : GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putBoolean(KEY_INPUT_VISIBILITY,
|
||||
textInput.getVisibility() == VISIBLE);
|
||||
ThreadItem replyItem = adapter.getReplyItem();
|
||||
if (replyItem != null) {
|
||||
outState.putByteArray(KEY_REPLY_ID,
|
||||
replyItem.getId().getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
if (textInput.isKeyboardOpen()) textInput.hideSoftKeyboard();
|
||||
supportFinishAfterTransition();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (textInput.getVisibility() == VISIBLE) {
|
||||
textInput.setVisibility(GONE);
|
||||
adapter.setReplyItem(null);
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemVisible(I item) {
|
||||
if (!item.isRead()) {
|
||||
item.setRead(true);
|
||||
markItemRead(item);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void markItemRead(I item);
|
||||
|
||||
@Override
|
||||
public void onReplyClick(I item) {
|
||||
showTextInput(item);
|
||||
}
|
||||
|
||||
protected void displaySnackbarShort(int stringId) {
|
||||
Snackbar snackbar = make(list, stringId, Snackbar.LENGTH_SHORT);
|
||||
snackbar.getView().setBackgroundResource(R.color.briar_primary);
|
||||
snackbar.show();
|
||||
}
|
||||
|
||||
protected void showTextInput(@Nullable I replyItem) {
|
||||
// An animation here would be an overkill because of the keyboard
|
||||
// popping up.
|
||||
// only clear the text when the input container was not visible
|
||||
if (textInput.getVisibility() != VISIBLE) {
|
||||
textInput.setVisibility(VISIBLE);
|
||||
textInput.setText("");
|
||||
}
|
||||
textInput.requestFocus();
|
||||
textInput.showSoftKeyboard();
|
||||
textInput.setHint(replyItem == null ? R.string.forum_new_message_hint :
|
||||
R.string.forum_message_reply_hint);
|
||||
adapter.setReplyItem(replyItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendClick(String text) {
|
||||
if (text.trim().length() == 0)
|
||||
return;
|
||||
I replyItem = adapter.getReplyItem();
|
||||
sendItem(text, replyItem);
|
||||
textInput.hideSoftKeyboard();
|
||||
textInput.setVisibility(GONE);
|
||||
adapter.setReplyItem(null);
|
||||
}
|
||||
|
||||
protected abstract void sendItem(String text, I replyToItem);
|
||||
|
||||
protected void addItem(final I item, boolean isLocal) {
|
||||
adapter.add(item);
|
||||
if (isLocal && adapter.isVisible(item)) {
|
||||
displaySnackbarShort(getItemPostedString());
|
||||
} else {
|
||||
Snackbar snackbar = Snackbar.make(list,
|
||||
isLocal ? getItemPostedString() : getItemReceivedString(),
|
||||
Snackbar.LENGTH_LONG);
|
||||
snackbar.getView().setBackgroundResource(R.color.briar_primary);
|
||||
snackbar.setActionTextColor(ContextCompat
|
||||
.getColor(ThreadListActivity.this,
|
||||
R.color.briar_button_positive));
|
||||
snackbar.setAction(R.string.show, new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
adapter.scrollTo(item);
|
||||
}
|
||||
});
|
||||
snackbar.getView().setBackgroundResource(R.color.briar_primary);
|
||||
snackbar.show();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract @StringRes int getItemPostedString();
|
||||
|
||||
protected abstract @StringRes int getItemReceivedString();
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user