Show join messages properly in the threaded conversation

This commit is contained in:
Torsten Grote
2016-10-21 12:36:40 -02:00
parent 349a34ffd8
commit 679b54b2b4
22 changed files with 274 additions and 178 deletions

View File

@@ -0,0 +1,122 @@
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.CallSuper;
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.api.nullsafety.NotNullByDefault;
import org.briarproject.util.StringUtils;
@UiThread
@NotNullByDefault
public class BaseThreadItemViewHolder<I extends ThreadItem>
extends RecyclerView.ViewHolder {
private final static int ANIMATION_DURATION = 5000;
private final ViewGroup layout;
private final TextView textView;
private final AuthorView author;
private final View topDivider;
public BaseThreadItemViewHolder(View v) {
super(v);
layout = (ViewGroup) v.findViewById(R.id.layout);
textView = (TextView) v.findViewById(R.id.text);
author = (AuthorView) v.findViewById(R.id.author);
topDivider = v.findViewById(R.id.top_divider);
}
// TODO improve encapsulation, so we don't need to pass the adapter here
@CallSuper
public void bind(final ThreadItemAdapter<I> adapter,
final ThreadItemListener<I> listener, final I item, int pos) {
textView.setText(StringUtils.trim(item.getText()));
if (pos == 0) {
topDivider.setVisibility(View.INVISIBLE);
} else {
topDivider.setVisibility(View.VISIBLE);
}
author.setAuthor(item.getAuthor());
author.setDate(item.getTimestamp());
author.setAuthorStatus(item.getStatus());
if (item.equals(adapter.getReplyItem())) {
layout.setBackgroundColor(ContextCompat
.getColor(getContext(), R.color.forum_cell_highlight));
} else if (item.equals(adapter.getAddedItem())) {
layout.setBackgroundColor(ContextCompat
.getColor(getContext(), R.color.forum_cell_highlight));
animateFadeOut(adapter, adapter.getAddedItem());
adapter.clearAddedItem();
} else {
layout.setBackgroundColor(ContextCompat
.getColor(getContext(), R.color.window_background));
}
}
private void animateFadeOut(final ThreadItemAdapter<I> adapter,
final I addedItem) {
setIsRecyclable(false);
ValueAnimator anim = new ValueAnimator();
adapter.addAnimatingItem(addedItem, anim);
ColorDrawable viewColor = (ColorDrawable) layout.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) {
layout.setBackgroundColor(
(Integer) valueAnimator.getAnimatedValue());
}
});
anim.setDuration(ANIMATION_DURATION);
anim.start();
}
protected Context getContext() {
return textView.getContext();
}
}

View File

@@ -1,13 +1,18 @@
package org.briarproject.android.threaded;
import android.support.annotation.UiThread;
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 javax.annotation.concurrent.NotThreadSafe;
import static org.briarproject.android.threaded.ThreadItemAdapter.UNDEFINED;
/* This class is not thread safe */
@UiThread
@NotThreadSafe
public abstract class ThreadItem implements MessageNode {
private final MessageId messageId;

View File

@@ -5,7 +5,11 @@ 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;
@@ -17,8 +21,8 @@ import java.util.Map;
import static android.support.v7.widget.RecyclerView.NO_POSITION;
public abstract class ThreadItemAdapter<I extends ThreadItem>
extends RecyclerView.Adapter<ThreadItemViewHolder<I>>
public class ThreadItemAdapter<I extends ThreadItem>
extends RecyclerView.Adapter<BaseThreadItemViewHolder<I>>
implements VersionedAdapter {
static final int UNDEFINED = -1;
@@ -42,7 +46,15 @@ public abstract class ThreadItemAdapter<I extends ThreadItem>
}
@Override
public void onBindViewHolder(ThreadItemViewHolder<I> ui, int position) {
public BaseThreadItemViewHolder<I> onCreateViewHolder(
ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_item_thread, parent, false);
return new ThreadItemViewHolder<>(v);
}
@Override
public void onBindViewHolder(BaseThreadItemViewHolder<I> ui, int position) {
I item = getVisibleItem(position);
if (item == null) return;
listener.onItemVisible(item);

View File

@@ -1,45 +1,30 @@
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 org.briarproject.api.nullsafety.NotNullByDefault;
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 {
@NotNullByDefault
public class ThreadItemViewHolder<I extends ThreadItem>
extends BaseThreadItemViewHolder<I> {
private final static int ANIMATION_DURATION = 5000;
private final TextView textView, lvlText, repliesText;
private final AuthorView author;
private final TextView lvlText, repliesText;
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,
@@ -51,21 +36,13 @@ public abstract class ThreadItemViewHolder<I extends ThreadItem>
}
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
@Override
public void bind(final ThreadItemAdapter<I> adapter,
final ThreadItemListener<I> listener, final I item, int pos) {
textView.setText(StringUtils.trim(item.getText()));
if (pos == 0) {
topDivider.setVisibility(View.INVISIBLE);
} else {
topDivider.setVisibility(View.VISIBLE);
}
super.bind(adapter, listener, item, pos);
for (int i = 0; i < lvls.length; i++) {
lvls[i].setVisibility(i < item.getLevel() ? VISIBLE : GONE);
@@ -76,9 +53,6 @@ public abstract class ThreadItemViewHolder<I extends ThreadItem>
} else {
lvlText.setVisibility(GONE);
}
author.setAuthor(item.getAuthor());
author.setDate(item.getTimestamp());
author.setAuthorStatus(item.getStatus());
int replies = adapter.getReplyCount(item);
if (replies == 0) {
@@ -110,18 +84,6 @@ public abstract class ThreadItemViewHolder<I extends ThreadItem>
} 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) {
@@ -131,52 +93,4 @@ public abstract class ThreadItemViewHolder<I extends ThreadItem>
});
}
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();
}
}

View File

@@ -1,5 +1,6 @@
package org.briarproject.android.threaded;
import android.content.Context;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
@@ -39,6 +40,8 @@ public interface ThreadListController<G extends NamedGroup, I extends ThreadItem
@UiThread
void onGroupRemoved();
Context getApplicationContext();
}
}

View File

@@ -52,7 +52,7 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
private volatile GroupId groupId;
protected ThreadListListener<H> listener;
protected volatile ThreadListListener<H> listener;
protected ThreadListControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, IdentityManager identityManager,
@@ -159,7 +159,7 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
for (H header : headers) {
if (!bodyCache.containsKey(header.getId())) {
bodyCache.put(header.getId(),
loadMessageBody(header.getId()));
loadMessageBody(header));
}
}
duration = System.currentTimeMillis() - now;
@@ -181,7 +181,7 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
protected abstract Collection<H> loadHeaders() throws DbException;
@DatabaseExecutor
protected abstract String loadMessageBody(MessageId id) throws DbException;
protected abstract String loadMessageBody(H header) throws DbException;
@Override
public void loadItem(final H header,
@@ -193,7 +193,7 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
long now = System.currentTimeMillis();
String body;
if (!bodyCache.containsKey(header.getId())) {
body = loadMessageBody(header.getId());
body = loadMessageBody(header);
bodyCache.put(header.getId(), body);
} else {
body = bodyCache.get(header.getId());