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
@@ -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 {
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 484 B |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
26
briar-android/res/drawable/contact_connected.xml
Normal 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>
|
||||
5
briar-android/res/drawable/contact_disconnected.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="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>
|
||||
5
briar-android/res/drawable/social_add_person.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="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>
|
||||
5
briar-android/res/drawable/social_remove_person.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="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>
|
||||
33
briar-android/res/layout/activity_contact_list.xml
Normal 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>
|
||||
51
briar-android/res/layout/list_item_contact.xml
Normal 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>
|
||||
@@ -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"
|
||||
|
||||
12
briar-android/res/menu/contact_actions.xml
Normal 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>
|
||||
12
briar-android/res/menu/contact_list_actions.xml
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||