Merge branch 'contact-list-activity' into 'master'

Use a RecyclerView for the Contact List

This is the first RecyclerView in the project. I am using a SortedList to keep the contacts sorted by latest activity.

Also, I removed the icon footer and moved the icon into ActionBar. I did the same with the long-press contact deletion action which eventually will move to the new contact details activity.

Several icons have been replaced by vector drawables and all the views are now defined in XML.

See merge request !38
This commit is contained in:
Torsten Grote
2015-12-28 16:40:36 +00:00
25 changed files with 527 additions and 247 deletions

View File

@@ -7,6 +7,7 @@ dependencies {
compile project(':briar-core')
compile fileTree(dir: 'libs', include: '*.jar')
compile "com.android.support:appcompat-v7:23.1.1"
compile 'com.android.support:recyclerview-v7:23.1.1'
}
android {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 484 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:alpha="0.56">
<path
android:fillColor="#FF000000"
android:pathData="M12,2 C6.48,2,2,6.48,2,12 S6.48,22,12,22 S22,17.52,22,12 S17.52,2,12,2 Z M12,20
C7.58,20,4,16.42,4,12 S7.58,4,12,4 S20,7.58,20,12 S16.42,20,12,20 Z" />
<path
android:pathData="M0,0 L24,0 L24,24 L0,24 Z" />
<path
android:fillColor="#95d220"
android:strokeWidth="0.76779664"
android:strokeLineJoin="round"
android:strokeLineCap="round"
android:pathData="M10.8972,19.9503 C6.5514,19.3493,3.43091,15.2154,4.0625,10.896
C4.55452,7.53099,7.09451,4.8236,10.394,4.14714
C14.2569,3.35517,18.1698,5.54347,19.5236,9.25295
C20.0698,10.7495,20.1616,12.4612,19.777,13.9758
C19.5457,14.8864,18.8106,16.3388,18.2072,17.0771
C16.4904,19.1779,13.581,20.3215,10.8973,19.9503 Z" />
</vector>

View 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="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm0,18c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
</vector>

View 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="M15,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zm-9,-2V7H4v3H1v2h3v3h2v-3h3v-2H6zm9,4c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
</vector>

View 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="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
</vector>

View File

@@ -0,0 +1,33 @@
<?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="match_parent"
android:orientation="vertical"
android:gravity="center">
<android.support.v7.widget.RecyclerView
android:id="@+id/contactList"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/list_item_contact"/>
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"
android:visibility="gone"/>
<TextView
android:id="@+id/emptyView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="@dimen/text_size_large"
android:text="@string/no_contacts"
android:visibility="gone"/>
</LinearLayout>

View File

@@ -0,0 +1,51 @@
<?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:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:padding="12dp">
<ImageView
android:id="@+id/bulbView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="@dimen/margin_medium"
android:layout_marginEnd="@dimen/margin_medium"
android:layout_gravity="center_vertical"
tools:src="@drawable/contact_disconnected"/>
<TextView
android:id="@+id/nameView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:layout_marginRight="@dimen/margin_small"
android:layout_marginEnd="@dimen/margin_small"
android:textSize="@dimen/text_size_medium"
android:gravity="center_vertical"
tools:text="This is a name of a contact. It can be quite long."/>
<TextView
android:id="@+id/dateView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginRight="@dimen/margin_small"
android:layout_marginEnd="@dimen/margin_small"
android:gravity="center_vertical"
android:textColor="@color/no_private_messages"
tools:text="Dec 24"/>
</LinearLayout>
<View style="@style/Divider.Horizontal"/>
</LinearLayout>

View File

@@ -5,10 +5,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:background="@color/horizontal_border"/>
<View style="@style/Divider.Horizontal"/>
<GridView
android:id="@+id/transportsView"

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_social_remove_person"
android:icon="@drawable/social_remove_person"
app:showAsAction="always"
android:title="@string/delete_contact"/>
</menu>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_social_add_person"
android:icon="@drawable/social_add_person"
app:showAsAction="always"
android:title="@string/add_contact_title"/>
</menu>

View File

@@ -126,4 +126,6 @@
<!-- Dialogs -->
<string name="dialog_title_lost_password">Lost password</string>
<string name="dialog_message_lost_password">Password recovery is not possible. Do you wish to delete your user, all contacts, and re-register ?</string>
<string name="dialog_title_delete_contact">Confirm Contact Deletion</string>
<string name="dialog_message_delete_contact">Are you sure that you want to remove this contact and all messages exchanged with this contact?</string>
</resources>

View File

@@ -33,4 +33,13 @@
<item name="android:textSize">@dimen/text_size_small</item>
<item name="android:textColor">@android:color/primary_text_light</item>
</style>
<style name="Divider">
<item name="android:background">?android:attr/listDivider</item>
</style>
<style name="Divider.Horizontal">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">1px</item>
</style>
</resources>

View File

@@ -1,28 +1,20 @@
package org.briarproject.android.contact;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.PorterDuff;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
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.View.OnCreateContextMenuListener;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ImageButton;
import android.widget.LinearLayout;
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.invitation.AddContactActivity;
import org.briarproject.android.util.HorizontalBorder;
import org.briarproject.android.util.ListLoadingProgressBar;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.contact.ContactManager;
@@ -36,43 +28,34 @@ import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventBus;
import org.briarproject.api.event.EventListener;
import org.briarproject.api.event.MessageAddedEvent;
import org.briarproject.api.identity.AuthorId;
import org.briarproject.api.messaging.MessagingManager;
import org.briarproject.api.messaging.PrivateMessageHeader;
import org.briarproject.api.plugins.ConnectionRegistry;
import org.briarproject.api.sync.GroupId;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Logger;
import javax.inject.Inject;
import static android.view.Gravity.CENTER;
import static android.view.Gravity.CENTER_HORIZONTAL;
import static android.view.Menu.NONE;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static android.widget.LinearLayout.VERTICAL;
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.util.CommonLayoutParams.MATCH_MATCH;
import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP;
import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP_1;
public class ContactListActivity extends BriarActivity
implements OnClickListener, OnItemClickListener, OnCreateContextMenuListener,
EventListener {
implements OnCreateContextMenuListener, EventListener {
private static final int MENU_ITEM_DELETE = 1;
private static final Logger LOG =
Logger.getLogger(ContactListActivity.class.getName());
@Inject private ConnectionRegistry connectionRegistry;
private TextView empty = null;
private ContactListAdapter adapter = null;
private ListView list = null;
private ListLoadingProgressBar loading = null;
private RecyclerView list = null;
private ProgressBar loading = null;
// Fields that are accessed from background threads must be volatile
@Inject private volatile ContactManager contactManager;
@@ -82,47 +65,28 @@ EventListener {
@Override
public void onCreate(Bundle state) {
super.onCreate(state);
LinearLayout layout = new LinearLayout(this);
layout.setLayoutParams(MATCH_MATCH);
layout.setOrientation(VERTICAL);
layout.setGravity(CENTER_HORIZONTAL);
empty = new TextView(this);
empty.setLayoutParams(MATCH_WRAP_1);
empty.setGravity(CENTER);
empty.setTextSize(18);
empty.setText(R.string.no_contacts);
empty.setVisibility(GONE);
layout.addView(empty);
setContentView(R.layout.activity_contact_list);
adapter = new ContactListAdapter(this);
list = new ListView(this);
list.setLayoutParams(MATCH_WRAP_1);
list = (RecyclerView) findViewById(R.id.contactList);
list.setLayoutManager(new LinearLayoutManager(this));
list.setAdapter(adapter);
list.setOnItemClickListener(this);
list.setOnCreateContextMenuListener(this);
list.setVisibility(GONE);
layout.addView(list);
// Show a notice when there are no contacts
empty = (TextView) findViewById(R.id.emptyView);
// Show a progress bar while the list is loading
loading = new ListLoadingProgressBar(this);
layout.addView(loading);
loading = (ProgressBar) findViewById(R.id.progressBar);
loading.setVisibility(VISIBLE);
}
layout.addView(new HorizontalBorder(this));
LinearLayout footer = new LinearLayout(this);
footer.setLayoutParams(MATCH_WRAP);
footer.setGravity(CENTER);
Resources res = getResources();
footer.setBackgroundColor(res.getColor(R.color.button_bar_background));
ImageButton addContactButton = new ImageButton(this);
addContactButton.setBackgroundResource(0);
addContactButton.setImageResource(R.drawable.social_add_person);
addContactButton.setOnClickListener(this);
footer.addView(addContactButton);
layout.addView(footer);
setContentView(layout);
@Override
public void onPause() {
super.onPause();
eventBus.removeListener(this);
}
@Override
@@ -132,12 +96,47 @@ EventListener {
loadContacts();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu items for use in the action bar
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.contact_list_actions, menu);
// adapt icon color to dark action bar
menu.findItem(R.id.action_social_add_person).getIcon().setColorFilter(
getResources().getColor(R.color.action_bar_text),
PorterDuff.Mode.SRC_IN);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
// Handle presses on the action bar items
switch (item.getItemId()) {
case R.id.action_social_add_person:
startActivity(new Intent(this, AddContactActivity.class));
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private void loadContacts() {
clearContacts();
runOnUiThread(new Runnable() {
public void run() {
empty.setVisibility(GONE);
list.setVisibility(GONE);
loading.setVisibility(VISIBLE);
}
});
runOnDbThread(new Runnable() {
public void run() {
try {
long now = System.currentTimeMillis();
List<ContactListItem> contacts =
new ArrayList<ContactListItem>();
for (Contact c : contactManager.getContacts()) {
try {
ContactId id = c.getId();
@@ -145,15 +144,20 @@ EventListener {
messagingManager.getConversationId(id);
Collection<PrivateMessageHeader> headers =
messagingManager.getMessageHeaders(id);
displayContact(c, conversation, headers);
boolean connected =
connectionRegistry.isConnected(c.getId());
contacts.add(new ContactListItem(c, connected,
conversation,
headers));
} catch (NoSuchContactException e) {
// Continue
}
}
displayContacts(contacts);
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Full load took " + duration + " ms");
hideProgressBar();
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
@@ -162,110 +166,19 @@ EventListener {
});
}
private void clearContacts() {
private void displayContacts(final List<ContactListItem> contacts) {
runOnUiThread(new Runnable() {
public void run() {
empty.setVisibility(GONE);
list.setVisibility(GONE);
loading.setVisibility(VISIBLE);
adapter.clear();
adapter.notifyDataSetChanged();
}
});
}
private void displayContact(final Contact c, final GroupId conversation,
final Collection<PrivateMessageHeader> headers) {
runOnUiThread(new Runnable() {
public void run() {
list.setVisibility(VISIBLE);
loading.setVisibility(GONE);
boolean connected = connectionRegistry.isConnected(c.getId());
// Remove the old item, if any
ContactListItem item = findItem(c.getId());
if (item != null) adapter.remove(item);
// Add a new item
adapter.add(new ContactListItem(c, connected, conversation,
headers));
adapter.sort(ContactListItemComparator.INSTANCE);
adapter.notifyDataSetChanged();
}
});
}
private void hideProgressBar() {
runOnUiThread(new Runnable() {
public void run() {
if (adapter.isEmpty()) empty.setVisibility(VISIBLE);
else list.setVisibility(VISIBLE);
loading.setVisibility(GONE);
}
});
}
private ContactListItem findItem(ContactId c) {
int count = adapter.getCount();
for (int i = 0; i < count; i++) {
ContactListItem item = adapter.getItem(i);
if (item.getContact().getId().equals(c)) return item;
}
return null; // Not found
}
@Override
public void onPause() {
super.onPause();
eventBus.removeListener(this);
}
public void onClick(View view) {
startActivity(new Intent(this, AddContactActivity.class));
}
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
ContactListItem item = adapter.getItem(position);
ContactId contactId = item.getContact().getId();
String contactName = item.getContact().getAuthor().getName();
GroupId groupId = item.getConversationId();
AuthorId localAuthorId = item.getContact().getLocalAuthorId();
Intent i = new Intent(this, ConversationActivity.class);
i.putExtra("briar.CONTACT_ID", contactId.getInt());
i.putExtra("briar.CONTACT_NAME", contactName);
i.putExtra("briar.GROUP_ID", groupId.getBytes());
i.putExtra("briar.LOCAL_AUTHOR_ID", localAuthorId.getBytes());
startActivity(i);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View view,
ContextMenu.ContextMenuInfo info) {
String delete = getString(R.string.delete_contact);
menu.add(NONE, MENU_ITEM_DELETE, NONE, delete);
}
@Override
public boolean onContextItemSelected(MenuItem menuItem) {
if (menuItem.getItemId() == MENU_ITEM_DELETE) {
ContextMenuInfo info = menuItem.getMenuInfo();
int position = ((AdapterContextMenuInfo) info).position;
ContactListItem item = adapter.getItem(position);
removeContact(item.getContact().getId());
String deleted = getString(R.string.contact_deleted_toast);
Toast.makeText(this, deleted, LENGTH_SHORT).show();
}
return true;
}
private void removeContact(final ContactId c) {
runOnDbThread(new Runnable() {
public void run() {
try {
contactManager.removeContact(c);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
if(contacts.size() > 0) {
list.setVisibility(VISIBLE);
empty.setVisibility(GONE);
} else {
list.setVisibility(GONE);
empty.setVisibility(VISIBLE);
}
loading.setVisibility(GONE);
adapter.addAll(contacts);
}
});
}
@@ -314,10 +227,11 @@ EventListener {
final Collection<PrivateMessageHeader> headers) {
runOnUiThread(new Runnable() {
public void run() {
ContactListItem item = findItem(c);
int position = adapter.findItemPosition(c);
ContactListItem item = adapter.getItem(position);
if (item != null) {
item.setHeaders(headers);
adapter.notifyDataSetChanged();
adapter.updateItem(position, item);
}
}
});
@@ -326,10 +240,10 @@ EventListener {
private void removeItem(final ContactId c) {
runOnUiThread(new Runnable() {
public void run() {
ContactListItem item = findItem(c);
ContactListItem item = adapter.findItem(c);
if (item != null) {
adapter.remove(item);
adapter.notifyDataSetChanged();
if (adapter.isEmpty()) {
empty.setVisibility(VISIBLE);
list.setVisibility(GONE);
@@ -342,10 +256,11 @@ EventListener {
private void setConnected(final ContactId c, final boolean connected) {
runOnUiThread(new Runnable() {
public void run() {
ContactListItem item = findItem(c);
int position = adapter.findItemPosition(c);
ContactListItem item = adapter.getItem(position);
if (item != null) {
item.setConnected(connected);
adapter.notifyDataSetChanged();
adapter.notifyItemChanged(position);
}
}
});

View File

@@ -1,80 +1,212 @@
package org.briarproject.android.contact;
import static android.text.TextUtils.TruncateAt.END;
import static android.view.Gravity.CENTER_VERTICAL;
import static android.widget.LinearLayout.HORIZONTAL;
import static org.briarproject.android.util.CommonLayoutParams.WRAP_WRAP_1;
import java.util.ArrayList;
import org.briarproject.R;
import org.briarproject.android.util.LayoutUtils;
import android.content.Context;
import android.content.Intent;
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.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
class ContactListAdapter extends ArrayAdapter<ContactListItem> {
import org.briarproject.R;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.identity.AuthorId;
import org.briarproject.api.sync.GroupId;
private final int pad;
import java.util.List;
ContactListAdapter(Context ctx) {
super(ctx, android.R.layout.simple_expandable_list_item_1,
new ArrayList<ContactListItem>());
pad = LayoutUtils.getPadding(ctx);
public class ContactListAdapter
extends RecyclerView.Adapter<ContactListAdapter.ContactHolder> {
private SortedList<ContactListItem> contacts =
new SortedList<ContactListItem>(ContactListItem.class,
new SortedList.Callback<ContactListItem>() {
@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(ContactListItem c1,
ContactListItem c2) {
// sort items by time
// and do not take unread messages into account
long time1 = c1.getTimestamp();
long time2 = c2.getTimestamp();
if (time1 < time2) return 1;
if (time1 > time2) return -1;
return 0;
}
@Override
public boolean areItemsTheSame(ContactListItem c1,
ContactListItem c2) {
return c1.getContact().getId().equals(c2.getContact().getId());
}
@Override
public boolean areContentsTheSame(ContactListItem c1,
ContactListItem c2) {
return c1.equals(c2);
}
});
private Context ctx;
public ContactListAdapter(Context context) {
ctx = context;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ContactListItem item = getItem(position);
Context ctx = getContext();
public ContactHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.list_item_contact, viewGroup, false);
return new ContactHolder(v);
}
@Override
public void onBindViewHolder(final ContactHolder ui, final int position) {
final ContactListItem item = getItem(position);
Resources res = ctx.getResources();
LinearLayout layout = new LinearLayout(ctx);
layout.setOrientation(HORIZONTAL);
layout.setGravity(CENTER_VERTICAL);
int unread = item.getUnreadCount();
if (unread > 0)
layout.setBackgroundColor(res.getColor(R.color.unread_background));
ImageView bulb = new ImageView(ctx);
bulb.setPadding(pad, pad, pad, pad);
if (item.isConnected())
bulb.setImageResource(R.drawable.contact_connected);
else bulb.setImageResource(R.drawable.contact_disconnected);
layout.addView(bulb);
TextView name = new TextView(ctx);
name.setLayoutParams(WRAP_WRAP_1);
name.setTextSize(18);
name.setSingleLine();
name.setEllipsize(END);
name.setPadding(0, pad, pad, pad);
String contactName = item.getContact().getAuthor().getName();
if (unread > 0) name.setText(contactName + " (" + unread + ")");
else name.setText(contactName);
layout.addView(name);
if (item.isEmpty()) {
TextView noMessages = new TextView(ctx);
noMessages.setPadding(pad, pad, pad, pad);
noMessages.setTextColor(res.getColor(R.color.no_private_messages));
noMessages.setText(R.string.no_private_messages);
layout.addView(noMessages);
} else {
TextView date = new TextView(ctx);
date.setPadding(pad, pad, pad, pad);
long timestamp = item.getTimestamp();
date.setText(DateUtils.getRelativeTimeSpanString(ctx, timestamp));
layout.addView(date);
if (unread > 0) {
ui.layout.setBackgroundColor(
res.getColor(R.color.unread_background));
}
return layout;
if (item.isConnected()) {
ui.bulb.setImageResource(R.drawable.contact_connected);
} else {
ui.bulb.setImageResource(R.drawable.contact_disconnected);
}
String contactName = item.getContact().getAuthor().getName();
if (unread > 0) {
ui.name.setText(contactName + " (" + unread + ")");
} else {
ui.name.setText(contactName);
}
if (item.isEmpty()) {
ui.date.setText(R.string.no_private_messages);
} else {
long timestamp = item.getTimestamp();
ui.date.setText(
DateUtils.getRelativeTimeSpanString(ctx, timestamp));
}
ui.layout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ContactId contactId = item.getContact().getId();
String contactName = item.getContact().getAuthor().getName();
GroupId groupId = item.getConversationId();
AuthorId localAuthorId = item.getContact().getLocalAuthorId();
Intent i = new Intent(ctx, ConversationActivity.class);
i.putExtra("briar.CONTACT_ID", contactId.getInt());
i.putExtra("briar.CONTACT_NAME", contactName);
i.putExtra("briar.GROUP_ID", groupId.getBytes());
i.putExtra("briar.LOCAL_AUTHOR_ID", localAuthorId.getBytes());
ctx.startActivity(i);
}
});
}
@Override
public int getItemCount() {
return contacts == null ? 0 : contacts.size();
}
public boolean isEmpty() {
return contacts == null || contacts.size() == 0;
}
public ContactListItem getItem(int position) {
if (position == -1 || contacts.size() <= position) {
return null; // Not found
}
return contacts.get(position);
}
public void updateItem(int position, ContactListItem item) {
contacts.updateItemAt(position, item);
}
public ContactListItem findItem(ContactId c) {
int count = getItemCount();
for (int i = 0; i < count; i++) {
ContactListItem item = getItem(i);
if (item.getContact().getId().equals(c)) return item;
}
return null; // Not found
}
public int findItemPosition(ContactId c) {
int count = getItemCount();
for (int i = 0; i < count; i++) {
ContactListItem item = getItem(i);
if (item.getContact().getId().equals(c)) return i;
}
return -1; // Not found
}
public void addAll(final List<ContactListItem> contacts) {
this.contacts.addAll(contacts);
}
public void add(final ContactListItem contact) {
this.contacts.add(contact);
}
public void remove(final ContactListItem contact) {
this.contacts.remove(contact);
}
public void clear() {
contacts.beginBatchedUpdates();
while(contacts.size() != 0) {
contacts.removeItemAt(0);
}
contacts.endBatchedUpdates();
}
public static class ContactHolder extends RecyclerView.ViewHolder {
public ViewGroup layout;
public ImageView bulb;
public TextView name;
public TextView date;
public ContactHolder(View v) {
super(v);
layout = (ViewGroup) v;
bulb = (ImageView) v.findViewById(R.id.bulbView);
name = (TextView) v.findViewById(R.id.nameView);
date = (TextView) v.findViewById(R.id.dateView);
}
}
}

View File

@@ -1,15 +0,0 @@
package org.briarproject.android.contact;
import java.util.Comparator;
class ContactListItemComparator implements Comparator<ContactListItem> {
static final ContactListItemComparator INSTANCE =
new ContactListItemComparator();
public int compare(ContactListItem a, ContactListItem b) {
String aName = a.getContact().getAuthor().getName();
String bName = b.getContact().getAuthor().getName();
return String.CASE_INSENSITIVE_ORDER.compare(aName, bName);
}
}

View File

@@ -1,10 +1,16 @@
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.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
@@ -15,6 +21,7 @@ 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;
@@ -66,6 +73,7 @@ import javax.inject.Inject;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
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;
@@ -161,6 +169,33 @@ implements EventListener, OnClickListener, OnItemClickListener {
loadHeaders();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu items for use in the action bar
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.contact_actions, menu);
// adapt icon color to dark action bar
menu.findItem(R.id.action_social_remove_person).getIcon().setColorFilter(
getResources().getColor(R.color.action_bar_text),
PorterDuff.Mode.SRC_IN);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
// Handle presses on the action bar items
switch (item.getItemId()) {
case R.id.action_social_remove_person:
askToRemoveContact();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private void loadContactAndGroup() {
runOnDbThread(new Runnable() {
public void run() {
@@ -478,4 +513,59 @@ implements EventListener, OnClickListener, OnItemClickListener {
i.putExtra("briar.POSITION", position);
startActivityForResult(i, REQUEST_READ);
}
private void askToRemoveContact() {
runOnUiThread(new Runnable() {
@Override
public void run() {
DialogInterface.OnClickListener okListener =
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
removeContact();
}
};
AlertDialog.Builder builder =
new AlertDialog.Builder(ConversationActivity.this);
builder.setTitle(
getString(R.string.dialog_title_delete_contact));
builder.setMessage(
getString(R.string.dialog_message_delete_contact));
builder.setPositiveButton(android.R.string.ok, okListener);
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
}
});
}
private void removeContact() {
runOnDbThread(new Runnable() {
public void run() {
try {
contactManager.removeContact(contactId);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
} finally {
finishAfterContactRemoved();
}
}
});
}
private void finishAfterContactRemoved() {
runOnUiThread(new Runnable() {
@Override
public void run() {
String deleted = getString(R.string.contact_deleted_toast);
Toast.makeText(ConversationActivity.this, deleted, LENGTH_SHORT)
.show();
finish();
}
});
}
}