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

@@ -2,30 +2,26 @@ package org.briarproject.android.contact;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.PorterDuff;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import org.briarproject.R;
import org.briarproject.android.BriarActivity;
import org.briarproject.android.util.LayoutUtils;
import org.briarproject.api.android.AndroidNotificationManager;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
@@ -77,12 +73,11 @@ import static android.widget.Toast.LENGTH_SHORT;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.android.contact.ReadPrivateMessageActivity.RESULT_PREV_NEXT;
import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP_1;
import static org.briarproject.api.messaging.PrivateMessageHeader.Status.DELIVERED;
import static org.briarproject.api.messaging.PrivateMessageHeader.Status.SENT;
public class ConversationActivity extends BriarActivity
implements EventListener, OnClickListener, OnItemClickListener {
implements EventListener, OnClickListener {
private static final int REQUEST_READ = 2;
private static final Logger LOG =
@@ -95,7 +90,7 @@ implements EventListener, OnClickListener, OnItemClickListener {
private TextView empty = null;
private ProgressBar loading = null;
private ConversationAdapter adapter = null;
private ListView list = null;
private RecyclerView list = null;
private EditText content = null;
private ImageButton sendButton = null;
@@ -133,20 +128,26 @@ implements EventListener, OnClickListener, OnItemClickListener {
loading.setVisibility(VISIBLE);
adapter = new ConversationAdapter(this);
list = new ListView(this) {
@Override
public void onSizeChanged(int w, int h, int oldw, int oldh) {
// Scroll to the bottom when the keyboard is shown
super.onSizeChanged(w, h, oldw, oldh);
setSelection(getCount() - 1);
}
};
list.setLayoutParams(MATCH_WRAP_1);
list.setDivider(null);
list = (RecyclerView) findViewById(R.id.conversationView);
list.setLayoutManager(new LinearLayoutManager(this));
list.setAdapter(adapter);
list.setOnItemClickListener(this);
list.setEmptyView(loading);
layout.addView(list, 0);
list.setVisibility(GONE);
// scroll down when opening keyboard
list.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v,
int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
if (bottom < oldBottom) {
list.postDelayed(new Runnable() {
@Override
public void run() {
list.scrollToPosition(adapter.getItemCount() - 1);
}
}, 100);
}
}
});
content = (EditText) findViewById(R.id.contentView);
sendButton = (ImageButton) findViewById(R.id.sendButton);
@@ -260,12 +261,10 @@ implements EventListener, OnClickListener, OnItemClickListener {
runOnUiThread(new Runnable() {
public void run() {
loading.setVisibility(GONE);
empty.setVisibility(VISIBLE);
list.setEmptyView(empty);
displayContactDetails();
sendButton.setEnabled(true);
adapter.clear();
if (!headers.isEmpty()) {
list.setVisibility(VISIBLE);
empty.setVisibility(GONE);
for (PrivateMessageHeader h : headers) {
ConversationItem item = new ConversationItem(h);
byte[] body = bodyCache.get(h.getId());
@@ -273,11 +272,12 @@ implements EventListener, OnClickListener, OnItemClickListener {
else item.setBody(body);
adapter.add(item);
}
adapter.sort(ConversationItemComparator.INSTANCE);
// Scroll to the bottom
list.setSelection(adapter.getCount() - 1);
list.scrollToPosition(adapter.getItemCount() - 1);
} else {
empty.setVisibility(VISIBLE);
list.setVisibility(GONE);
}
adapter.notifyDataSetChanged();
}
});
}
@@ -306,14 +306,18 @@ implements EventListener, OnClickListener, OnItemClickListener {
runOnUiThread(new Runnable() {
public void run() {
bodyCache.put(m, body);
int count = adapter.getCount();
int count = adapter.getItemCount();
for (int i = 0; i < count; i++) {
ConversationItem item = adapter.getItem(i);
if (item.getHeader().getId().equals(m)) {
item.setBody(body);
adapter.notifyDataSetChanged();
adapter.notifyItemChanged(i);
// Scroll to the bottom
list.setSelection(count - 1);
list.scrollToPosition(count - 1);
return;
}
}
@@ -326,7 +330,7 @@ implements EventListener, OnClickListener, OnItemClickListener {
super.onActivityResult(request, result, data);
if (request == REQUEST_READ && result == RESULT_PREV_NEXT) {
int position = data.getIntExtra("briar.POSITION", -1);
if (position >= 0 && position < adapter.getCount())
if (position >= 0 && position < adapter.getItemCount())
displayMessage(position);
}
}
@@ -341,7 +345,7 @@ implements EventListener, OnClickListener, OnItemClickListener {
private void markMessagesRead() {
notificationManager.clearPrivateMessageNotification(contactId);
List<MessageId> unread = new ArrayList<MessageId>();
int count = adapter.getCount();
int count = adapter.getItemCount();
for (int i = 0; i < count; i++) {
PrivateMessageHeader h = adapter.getItem(i).getHeader();
if (!h.isRead()) unread.add(h.getId());
@@ -381,6 +385,8 @@ implements EventListener, OnClickListener, OnItemClickListener {
GroupId g = ((MessageAddedEvent) e).getGroupId();
if (g.equals(groupId)) {
LOG.info("Message added, reloading");
// TODO: find a way of not needing to reload the entire
// conversation just because one message was added
loadHeaders();
}
} else if (e instanceof MessagesSentEvent) {
@@ -417,16 +423,14 @@ implements EventListener, OnClickListener, OnItemClickListener {
runOnUiThread(new Runnable() {
public void run() {
Set<MessageId> messages = new HashSet<MessageId>(messageIds);
boolean changed = false;
int count = adapter.getCount();
int count = adapter.getItemCount();
for (int i = 0; i < count; i++) {
ConversationItem item = adapter.getItem(i);
if (messages.contains(item.getHeader().getId())) {
item.setStatus(status);
changed = true;
adapter.notifyItemChanged(i);
}
}
if (changed) adapter.notifyDataSetChanged();
}
});
}
@@ -444,7 +448,7 @@ implements EventListener, OnClickListener, OnItemClickListener {
private long getMinTimestampForNewMessage() {
// Don't use an earlier timestamp than the newest message
long timestamp = 0;
int count = adapter.getCount();
int count = adapter.getItemCount();
for (int i = 0; i < count; i++) {
long t = adapter.getItem(i).getHeader().getTimestamp();
if (t > timestamp) timestamp = t;
@@ -485,11 +489,6 @@ implements EventListener, OnClickListener, OnItemClickListener {
});
}
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
displayMessage(position);
}
private void displayMessage(int position) {
ConversationItem item = adapter.getItem(position);
PrivateMessageHeader header = item.getHeader();