Merge branch '9-conversation-view' into 'master'
Overhauled Conversation View with Message Bubbles The Conversation View now uses a RecyclerView with conversation bubbles in alternating colors and vector drawables to indicate message state. The conversation bubbles have been taken from Telegram and can be replaced by a UX designer later. There's also a special bubble for unread messages, so they are not overlooked when they come in delayed. This commit also addresses #9, because message text can now be selected and copied. This is done by using android:textIsSelectable="true" which only works for API level 11 or higher. If we want copy and paste on lower API levels, additional measures have to be implemented. See merge request !36
|
Before Width: | Height: | Size: 479 B |
|
Before Width: | Height: | Size: 284 B |
|
Before Width: | Height: | Size: 323 B |
BIN
briar-android/res/drawable-hdpi/msg_in.9.png
Normal file
|
After Width: | Height: | Size: 957 B |
BIN
briar-android/res/drawable-hdpi/msg_in_unread.9.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
briar-android/res/drawable-hdpi/msg_out.9.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 313 B |
|
Before Width: | Height: | Size: 238 B |
|
Before Width: | Height: | Size: 237 B |
BIN
briar-android/res/drawable-mdpi/msg_in.9.png
Normal file
|
After Width: | Height: | Size: 669 B |
BIN
briar-android/res/drawable-mdpi/msg_in_unread.9.png
Normal file
|
After Width: | Height: | Size: 1014 B |
BIN
briar-android/res/drawable-mdpi/msg_out.9.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 627 B |
|
Before Width: | Height: | Size: 392 B |
|
Before Width: | Height: | Size: 443 B |
BIN
briar-android/res/drawable-xhdpi/msg_in.9.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
briar-android/res/drawable-xhdpi/msg_in_unread.9.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
briar-android/res/drawable-xhdpi/msg_out.9.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 683 B |
BIN
briar-android/res/drawable-xxhdpi/msg_in.9.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
briar-android/res/drawable-xxhdpi/msg_in_unread.9.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
briar-android/res/drawable-xxhdpi/msg_out.9.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 969 B |
5
briar-android/res/drawable/message_delivered.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:alpha="0.56" android:height="16dp"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="16dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M18,7l-1.41,-1.41 -6.34,6.34 1.41,1.41L18,7zm4.24,-1.41L11.66,16.17 7.48,12l-1.41,1.41L11.66,19l12,-12 -1.42,-1.41zM0.41,13.41L6,19l1.41,-1.41L1.83,12 0.41,13.41z"/>
|
||||
</vector>
|
||||
5
briar-android/res/drawable/message_sent.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:alpha="0.56" android:height="16dp"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="16dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
|
||||
</vector>
|
||||
5
briar-android/res/drawable/message_stored.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:alpha="0.56" android:height="16dp"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="16dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillAlpha=".9" android:fillColor="#FF000000" android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"/>
|
||||
</vector>
|
||||
5
briar-android/res/drawable/social_send_now.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:alpha="0.56" android:height="24dp"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"/>
|
||||
</vector>
|
||||
@@ -1,12 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/layout"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<!-- ListView will get inserted here -->
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/conversationView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:scrollbars="vertical"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/listLoadingProgressBar"
|
||||
@@ -15,7 +21,8 @@
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:layout_weight="1"
|
||||
android:indeterminate="true"/>
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/emptyView"
|
||||
@@ -26,7 +33,8 @@
|
||||
android:gravity="center"
|
||||
android:padding="@dimen/margin_large"
|
||||
android:textSize="@dimen/text_size_large"
|
||||
android:text="@string/no_private_messages"/>
|
||||
android:text="@string/no_private_messages"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
@@ -39,9 +47,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/button_bar_background"
|
||||
android:paddingLeft="@dimen/margin_medium"
|
||||
android:paddingStart="@dimen/margin_medium"
|
||||
android:paddingRight="@dimen/margin_medium"
|
||||
android:paddingEnd="@dimen/margin_medium">
|
||||
android:paddingStart="@dimen/margin_medium">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/contentView"
|
||||
@@ -53,12 +59,16 @@
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/sendButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="38dp"
|
||||
android:layout_height="38dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:src="@drawable/social_send_now"
|
||||
android:background="@color/button_bar_background"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:scaleType="fitEnd"
|
||||
android:contentDescription="@string/send"
|
||||
android:layout_gravity="center"/>
|
||||
android:paddingRight="@dimen/margin_medium"
|
||||
android:paddingEnd="@dimen/margin_medium"
|
||||
android:paddingBottom="@dimen/margin_medium"/>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
43
briar-android/res/layout/list_item_msg_in.xml
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingRight="@dimen/margin_medium"
|
||||
android:paddingEnd="@dimen/margin_medium"
|
||||
android:paddingTop="@dimen/margin_small"
|
||||
android:paddingBottom="@dimen/margin_small">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/msgLayout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="left|start"
|
||||
android:background="@drawable/msg_in"
|
||||
android:paddingLeft="17dp"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingRight="7dp"
|
||||
android:paddingBottom="5dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/msgBody"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="80dp"
|
||||
android:textIsSelectable="true"
|
||||
tools:text="Short message"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/msgTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="10sp"
|
||||
android:textColor="@color/private_message_date"
|
||||
android:layout_below="@+id/msgBody"
|
||||
tools:text="Dec 24, 13:37"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
||||
55
briar-android/res/layout/list_item_msg_out.xml
Normal file
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="@dimen/margin_medium"
|
||||
android:paddingStart="@dimen/margin_medium"
|
||||
android:paddingTop="@dimen/margin_small"
|
||||
android:paddingBottom="@dimen/margin_small">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/msgLayout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="right|end"
|
||||
android:background="@drawable/msg_out"
|
||||
android:paddingLeft="7dp"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingRight="17dp"
|
||||
android:paddingBottom="5dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/msgBody"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textIsSelectable="true"
|
||||
android:minWidth="80dp"
|
||||
tools:text="This is a long long long message that spans over several lines.\n\nIt ends here."/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/msgTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/msgBody"
|
||||
android:layout_toLeftOf="@+id/msgStatus"
|
||||
android:textSize="10sp"
|
||||
android:textColor="@color/private_message_date"
|
||||
android:singleLine="true"
|
||||
tools:text="Dec 24, 13:37"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/msgStatus"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBottom="@+id/msgTime"
|
||||
android:layout_alignRight="@+id/msgBody"
|
||||
android:layout_alignEnd="@+id/msgBody"
|
||||
android:layout_marginLeft="3dp"
|
||||
tools:src="@drawable/message_delivered"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -5,7 +5,6 @@
|
||||
<color name="action_bar_background">#2D3E50</color>
|
||||
<color name="button_bar_background">#FFFFFF</color>
|
||||
<color name="dashboard_background">#FFFFFF</color>
|
||||
<color name="private_message_background">#FFFFFF</color>
|
||||
<color name="private_message_date">#AAAAAA</color>
|
||||
<color name="unread_background">#FFFFFF</color>
|
||||
<color name="horizontal_border">#CCCCCC</color>
|
||||
|
||||
@@ -2,30 +2,27 @@ 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.Build;
|
||||
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 +74,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 +91,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,27 +129,29 @@ 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);
|
||||
int pad = LayoutUtils.getPadding(this);
|
||||
list.setPadding(0, pad, 0, pad);
|
||||
list.setClipToPadding(false);
|
||||
// Make the dividers the same colour as the background
|
||||
Resources res = getResources();
|
||||
int background = res.getColor(android.R.color.transparent);
|
||||
list.setDivider(new ColorDrawable(background));
|
||||
list.setDividerHeight(pad);
|
||||
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
|
||||
if (Build.VERSION.SDK_INT >= 11) {
|
||||
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);
|
||||
@@ -267,12 +265,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());
|
||||
@@ -280,11 +276,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();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -313,14 +310,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;
|
||||
}
|
||||
}
|
||||
@@ -333,7 +334,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);
|
||||
}
|
||||
}
|
||||
@@ -348,7 +349,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());
|
||||
@@ -388,6 +389,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) {
|
||||
@@ -424,16 +427,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();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -451,7 +452,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;
|
||||
@@ -492,11 +493,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();
|
||||
|
||||
@@ -1,115 +1,199 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
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.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.util.ElasticHorizontalSpace;
|
||||
import org.briarproject.android.util.LayoutUtils;
|
||||
import org.briarproject.api.messaging.PrivateMessageHeader;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static android.view.Gravity.BOTTOM;
|
||||
import static android.view.Gravity.LEFT;
|
||||
import static android.widget.LinearLayout.HORIZONTAL;
|
||||
import static android.widget.LinearLayout.VERTICAL;
|
||||
import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP;
|
||||
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> {
|
||||
|
||||
private final int pad;
|
||||
private static final int MSG_OUT = 0;
|
||||
private static final int MSG_IN = 1;
|
||||
private static final int MSG_IN_UNREAD = 2;
|
||||
|
||||
ConversationAdapter(Context ctx) {
|
||||
super(ctx, android.R.layout.simple_expandable_list_item_1,
|
||||
new ArrayList<ConversationItem>());
|
||||
pad = LayoutUtils.getPadding(ctx);
|
||||
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) {
|
||||
public int getItemViewType(int position) {
|
||||
// return different type for incoming and outgoing (local) messages
|
||||
PrivateMessageHeader header = getItem(position).getHeader();
|
||||
if (header.isLocal()) {
|
||||
return MSG_OUT;
|
||||
} else if (header.isRead()) {
|
||||
return MSG_IN;
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
|
||||
return new MessageHolder(v, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(final MessageHolder ui, final int position) {
|
||||
ConversationItem item = getItem(position);
|
||||
PrivateMessageHeader header = item.getHeader();
|
||||
Context ctx = getContext();
|
||||
Resources res = ctx.getResources();
|
||||
|
||||
LinearLayout layout = new LinearLayout(ctx);
|
||||
layout.setOrientation(VERTICAL);
|
||||
if (header.isLocal()) layout.setPadding(3 * pad, 0, 0, 0);
|
||||
else layout.setPadding(0, 0, 3 * pad, 0);
|
||||
|
||||
int background = res.getColor(R.color.private_message_background);
|
||||
|
||||
View content;
|
||||
if (item.getBody() == null) {
|
||||
TextView ellipsis = new TextView(ctx);
|
||||
ellipsis.setText("\u2026");
|
||||
content = ellipsis;
|
||||
} else if (header.getContentType().equals("text/plain")) {
|
||||
TextView text = new TextView(ctx);
|
||||
text.setText(StringUtils.fromUtf8(item.getBody()));
|
||||
content = text;
|
||||
} else {
|
||||
ImageButton attachment = new ImageButton(ctx);
|
||||
attachment.setImageResource(R.drawable.content_attachment);
|
||||
content = attachment;
|
||||
}
|
||||
content.setLayoutParams(MATCH_WRAP);
|
||||
content.setBackgroundColor(background);
|
||||
content.setPadding(pad, pad, pad, 0);
|
||||
layout.addView(content);
|
||||
|
||||
if (header.isLocal()) {
|
||||
LinearLayout footer = new LinearLayout(ctx);
|
||||
footer.setLayoutParams(MATCH_WRAP);
|
||||
footer.setOrientation(HORIZONTAL);
|
||||
footer.setGravity(BOTTOM);
|
||||
footer.setPadding(pad, 0, pad, pad);
|
||||
footer.setBackgroundColor(background);
|
||||
|
||||
footer.addView(new ElasticHorizontalSpace(ctx));
|
||||
|
||||
ImageView status = new ImageView(ctx);
|
||||
status.setPadding(0, 0, pad, 0);
|
||||
if (item.getStatus() == DELIVERED) {
|
||||
status.setImageResource(R.drawable.message_delivered);
|
||||
ui.status.setImageResource(R.drawable.message_delivered);
|
||||
} else if (item.getStatus() == SENT) {
|
||||
status.setImageResource(R.drawable.message_sent);
|
||||
ui.status.setImageResource(R.drawable.message_sent);
|
||||
} else {
|
||||
status.setImageResource(R.drawable.message_stored);
|
||||
ui.status.setImageResource(R.drawable.message_stored);
|
||||
}
|
||||
footer.addView(status);
|
||||
} else if (!header.isRead()) {
|
||||
int bottom = ui.layout.getPaddingBottom();
|
||||
int top = ui.layout.getPaddingTop();
|
||||
int right = ui.layout.getPaddingRight();
|
||||
int left = ui.layout.getPaddingLeft();
|
||||
|
||||
TextView date = new TextView(ctx);
|
||||
date.setTextColor(res.getColor(R.color.private_message_date));
|
||||
long timestamp = header.getTimestamp();
|
||||
date.setText(DateUtils.getRelativeTimeSpanString(ctx, timestamp));
|
||||
footer.addView(date);
|
||||
// show unread messages in different color to not miss them
|
||||
ui.layout.setBackgroundResource(R.drawable.msg_in_unread);
|
||||
|
||||
layout.addView(footer);
|
||||
} else {
|
||||
TextView date = new TextView(ctx);
|
||||
date.setLayoutParams(MATCH_WRAP);
|
||||
date.setGravity(LEFT);
|
||||
date.setTextColor(res.getColor(R.color.private_message_date));
|
||||
date.setBackgroundColor(background);
|
||||
date.setPadding(pad, 0, pad, pad);
|
||||
long timestamp = header.getTimestamp();
|
||||
date.setText(DateUtils.getRelativeTimeSpanString(ctx, timestamp));
|
||||
layout.addView(date);
|
||||
// re-apply the previous padding due to bug in some Android versions
|
||||
// see: https://code.google.com/p/android/issues/detail?id=17885
|
||||
ui.layout.setPadding(left, top, right, bottom);
|
||||
}
|
||||
|
||||
return layout;
|
||||
if (item.getBody() == null) {
|
||||
ui.body.setText("\u2026");
|
||||
} else if (header.getContentType().equals("text/plain")) {
|
||||
ui.body.setText(StringUtils.fromUtf8(item.getBody()));
|
||||
} else {
|
||||
// TODO support other content types
|
||||
}
|
||||
|
||||
long timestamp = header.getTimestamp();
|
||||
ui.date.setText(DateUtils.getRelativeTimeSpanString(ctx, timestamp));
|
||||
}
|
||||
|
||||
@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 message) {
|
||||
this.messages.add(message);
|
||||
}
|
||||
|
||||
public void remove(final ConversationItem message) {
|
||||
this.messages.remove(message);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
class ConversationItemComparator implements Comparator<ConversationItem> {
|
||||
|
||||
static final ConversationItemComparator INSTANCE =
|
||||
new ConversationItemComparator();
|
||||
|
||||
public int compare(ConversationItem a, ConversationItem b) {
|
||||
// The oldest message comes first
|
||||
long aTime = a.getHeader().getTimestamp();
|
||||
long bTime = b.getHeader().getTimestamp();
|
||||
if (aTime < bTime) return -1;
|
||||
if (aTime > bTime) return 1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,7 @@ import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP_1;
|
||||
import static org.briarproject.android.util.CommonLayoutParams.WRAP_WRAP_1;
|
||||
import static org.briarproject.api.identity.Author.Status.VERIFIED;
|
||||
|
||||
@Deprecated
|
||||
public class ReadPrivateMessageActivity extends BriarActivity
|
||||
implements OnClickListener {
|
||||
|
||||
|
||||