Use a RecyclerView for the ConversationView and

properly notify the view adapter of dataset changes
in order to avoid invalidating the entire dataset when not absolutely necessary.

This change also shows unread messages in a different color,
so users do not fail to notice delayed messages.
This commit is contained in:
Torsten Grote
2015-12-23 15:33:41 -02:00
parent 68cd1ff28c
commit e98d4f2260
12 changed files with 210 additions and 98 deletions

View File

@@ -1,11 +1,12 @@
package org.briarproject.android.contact;
import android.content.Context;
import android.support.v7.util.SortedList;
import android.support.v7.widget.RecyclerView;
import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
@@ -13,57 +14,177 @@ import org.briarproject.R;
import org.briarproject.api.messaging.PrivateMessageHeader;
import org.briarproject.util.StringUtils;
import java.util.ArrayList;
import static org.briarproject.api.messaging.PrivateMessageHeader.Status.DELIVERED;
import static org.briarproject.api.messaging.PrivateMessageHeader.Status.SENT;
class ConversationAdapter extends ArrayAdapter<ConversationItem> {
class ConversationAdapter extends
RecyclerView.Adapter<ConversationAdapter.MessageHolder> {
ConversationAdapter(Context ctx) {
super(ctx, android.R.layout.simple_expandable_list_item_1,
new ArrayList<ConversationItem>());
private static final int MSG_OUT = 0;
private static final int MSG_IN = 1;
private static final int MSG_IN_UNREAD = 2;
private SortedList<ConversationItem> messages =
new SortedList<ConversationItem>(ConversationItem.class,
new SortedList.Callback<ConversationItem>() {
@Override
public void onInserted(int position, int count) {
notifyItemRangeInserted(position, count);
}
@Override
public void onChanged(int position, int count) {
notifyItemRangeChanged(position, count);
}
@Override
public void onMoved(int fromPosition, int toPosition) {
notifyItemMoved(fromPosition, toPosition);
}
@Override
public void onRemoved(int position, int count) {
notifyItemRangeRemoved(position, count);
}
@Override
public int compare(ConversationItem c1,
ConversationItem c2) {
long time1 = c1.getHeader().getTimestamp();
long time2 = c2.getHeader().getTimestamp();
if (time1 < time2) return -1;
if (time1 > time2) return 1;
return 0;
}
@Override
public boolean areItemsTheSame(ConversationItem c1,
ConversationItem c2) {
return c1.getHeader().getId()
.equals(c2.getHeader().getId());
}
@Override
public boolean areContentsTheSame(ConversationItem c1,
ConversationItem c2) {
return c1.equals(c2);
}
});
private Context ctx;
public ConversationAdapter(Context context) {
ctx = context;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ConversationItem item = getItem(position);
PrivateMessageHeader header = item.getHeader();
Context ctx = getContext();
LayoutInflater inflater = (LayoutInflater) ctx.getSystemService
(Context.LAYOUT_INFLATER_SERVICE);
View v;
public int getItemViewType(int position) {
// return different type for incoming and outgoing (local) messages
PrivateMessageHeader header = getItem(position).getHeader();
if (header.isLocal()) {
v = inflater.inflate(R.layout.list_item_msg_out, null);
ImageView status = (ImageView) v.findViewById(R.id.msgStatus);
if (item.getStatus() == DELIVERED) {
status.setImageResource(R.drawable.message_delivered);
} else if (item.getStatus() == SENT) {
status.setImageResource(R.drawable.message_sent);
} else {
status.setImageResource(R.drawable.message_stored);
}
return MSG_OUT;
} else if (header.isRead()) {
return MSG_IN;
} else {
v = inflater.inflate(R.layout.list_item_msg_in, null);
return MSG_IN_UNREAD;
}
}
@Override
public MessageHolder onCreateViewHolder(ViewGroup viewGroup, int type) {
View v;
// outgoing message (local)
if (type == MSG_OUT) {
v = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.list_item_msg_out, viewGroup, false);
}
// incoming message (non-local)
else {
v = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.list_item_msg_in, viewGroup, false);
}
TextView body = (TextView) v.findViewById(R.id.msgBody);
return new MessageHolder(v, type);
}
@Override
public void onBindViewHolder(final MessageHolder ui, final int position) {
ConversationItem item = getItem(position);
PrivateMessageHeader header = item.getHeader();
if (header.isLocal()) {
if (item.getStatus() == DELIVERED) {
ui.status.setImageResource(R.drawable.message_delivered);
} else if (item.getStatus() == SENT) {
ui.status.setImageResource(R.drawable.message_sent);
} else {
ui.status.setImageResource(R.drawable.message_stored);
}
} else if (!header.isRead()) {
// show unread messages in different color to not miss them
ui.layout.setBackgroundResource(R.drawable.msg_in_unread);
}
if (item.getBody() == null) {
body.setText("\u2026");
ui.body.setText("\u2026");
} else if (header.getContentType().equals("text/plain")) {
body.setText(StringUtils.fromUtf8(item.getBody()));
ui.body.setText(StringUtils.fromUtf8(item.getBody()));
} else {
// TODO support other content types
}
TextView date = (TextView) v.findViewById(R.id.msgTime);
long timestamp = header.getTimestamp();
date.setText(DateUtils.getRelativeTimeSpanString(ctx, timestamp));
ui.date.setText(DateUtils.getRelativeTimeSpanString(ctx, timestamp));
}
return v;
@Override
public int getItemCount() {
return messages == null ? 0 : messages.size();
}
public boolean isEmpty() {
return messages == null || messages.size() == 0;
}
public ConversationItem getItem(int position) {
return messages.get(position);
}
public void add(final ConversationItem contact) {
this.messages.add(contact);
}
public void remove(final ConversationItem contact) {
this.messages.remove(contact);
}
public void clear() {
this.messages.beginBatchedUpdates();
while(messages.size() != 0) {
messages.removeItemAt(0);
}
this.messages.endBatchedUpdates();
}
public static class MessageHolder extends RecyclerView.ViewHolder {
public ViewGroup layout;
public TextView body;
public TextView date;
public ImageView status;
public MessageHolder(View v, int type) {
super(v);
layout = (ViewGroup) v.findViewById(R.id.msgLayout);
body = (TextView) v.findViewById(R.id.msgBody);
date = (TextView) v.findViewById(R.id.msgTime);
// outgoing message (local)
if (type == MSG_OUT) {
status = (ImageView) v.findViewById(R.id.msgStatus);
}
}
}
}