diff --git a/briar-android/build.gradle b/briar-android/build.gradle index 3e6d17c84737136c217d1038a2df68590f3a43aa..f06ca62b13bf98364984b0d5a32fabfd16e240ae 100644 --- a/briar-android/build.gradle +++ b/briar-android/build.gradle @@ -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 { diff --git a/briar-android/res/drawable-hdpi/contact_connected.png b/briar-android/res/drawable-hdpi/contact_connected.png deleted file mode 100644 index 9fa452b33cdc41c6771b7df4bd6d24c60458d851..0000000000000000000000000000000000000000 Binary files a/briar-android/res/drawable-hdpi/contact_connected.png and /dev/null differ diff --git a/briar-android/res/drawable-hdpi/contact_disconnected.png b/briar-android/res/drawable-hdpi/contact_disconnected.png deleted file mode 100644 index 9de4968499c5c464a53beb423088bbe7c618b5b4..0000000000000000000000000000000000000000 Binary files a/briar-android/res/drawable-hdpi/contact_disconnected.png and /dev/null differ diff --git a/briar-android/res/drawable-hdpi/social_add_person.png b/briar-android/res/drawable-hdpi/social_add_person.png deleted file mode 100644 index 38b91cc7ce29dac49f4f048c4e7738bde2e41bf1..0000000000000000000000000000000000000000 Binary files a/briar-android/res/drawable-hdpi/social_add_person.png and /dev/null differ diff --git a/briar-android/res/drawable-mdpi/contact_connected.png b/briar-android/res/drawable-mdpi/contact_connected.png deleted file mode 100644 index d91970da5b783896c4d9054cbd9e37538fe20d2e..0000000000000000000000000000000000000000 Binary files a/briar-android/res/drawable-mdpi/contact_connected.png and /dev/null differ diff --git a/briar-android/res/drawable-mdpi/contact_disconnected.png b/briar-android/res/drawable-mdpi/contact_disconnected.png deleted file mode 100644 index bef15c40b285d783f8586d6e0af2476ec8c609d7..0000000000000000000000000000000000000000 Binary files a/briar-android/res/drawable-mdpi/contact_disconnected.png and /dev/null differ diff --git a/briar-android/res/drawable-mdpi/social_add_person.png b/briar-android/res/drawable-mdpi/social_add_person.png deleted file mode 100644 index 2c9899ec8a0368e314306dd6525edd74932f47d2..0000000000000000000000000000000000000000 Binary files a/briar-android/res/drawable-mdpi/social_add_person.png and /dev/null differ diff --git a/briar-android/res/drawable-xhdpi/contact_connected.png b/briar-android/res/drawable-xhdpi/contact_connected.png deleted file mode 100644 index 4341f01a1a16797592c31e81c119d260cff7f9c9..0000000000000000000000000000000000000000 Binary files a/briar-android/res/drawable-xhdpi/contact_connected.png and /dev/null differ diff --git a/briar-android/res/drawable-xhdpi/contact_disconnected.png b/briar-android/res/drawable-xhdpi/contact_disconnected.png deleted file mode 100644 index d0499ddf1ffb6335c4b4f48d42ffed128d863a62..0000000000000000000000000000000000000000 Binary files a/briar-android/res/drawable-xhdpi/contact_disconnected.png and /dev/null differ diff --git a/briar-android/res/drawable-xhdpi/social_add_person.png b/briar-android/res/drawable-xhdpi/social_add_person.png deleted file mode 100644 index 2cefca64503310db99633e3b989344bc96c8b75f..0000000000000000000000000000000000000000 Binary files a/briar-android/res/drawable-xhdpi/social_add_person.png and /dev/null differ diff --git a/briar-android/res/drawable/contact_connected.xml b/briar-android/res/drawable/contact_connected.xml new file mode 100644 index 0000000000000000000000000000000000000000..95e7a0bc06c3edc780303abda49339d19d31b8fc --- /dev/null +++ b/briar-android/res/drawable/contact_connected.xml @@ -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> \ No newline at end of file diff --git a/briar-android/res/drawable/contact_disconnected.xml b/briar-android/res/drawable/contact_disconnected.xml new file mode 100644 index 0000000000000000000000000000000000000000..d8ee38c1e146d5c49c6f7464358c6e1664d6e7ff --- /dev/null +++ b/briar-android/res/drawable/contact_disconnected.xml @@ -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> diff --git a/briar-android/res/drawable/social_add_person.xml b/briar-android/res/drawable/social_add_person.xml new file mode 100644 index 0000000000000000000000000000000000000000..982183dea2a080d264a93261192e532907099735 --- /dev/null +++ b/briar-android/res/drawable/social_add_person.xml @@ -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> diff --git a/briar-android/res/drawable/social_remove_person.xml b/briar-android/res/drawable/social_remove_person.xml new file mode 100644 index 0000000000000000000000000000000000000000..4adacc148f6a1be74b7fa11f15d2b6a6201e76bb --- /dev/null +++ b/briar-android/res/drawable/social_remove_person.xml @@ -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> diff --git a/briar-android/res/layout/activity_contact_list.xml b/briar-android/res/layout/activity_contact_list.xml new file mode 100644 index 0000000000000000000000000000000000000000..acde6fdc93da9c68106d74c21e77dd8747e2c77b --- /dev/null +++ b/briar-android/res/layout/activity_contact_list.xml @@ -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> \ No newline at end of file diff --git a/briar-android/res/layout/list_item_contact.xml b/briar-android/res/layout/list_item_contact.xml new file mode 100644 index 0000000000000000000000000000000000000000..69ec0383e31bda3ec65b01e7fea2faab620448b2 --- /dev/null +++ b/briar-android/res/layout/list_item_contact.xml @@ -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> \ No newline at end of file diff --git a/briar-android/res/layout/transports_list.xml b/briar-android/res/layout/transports_list.xml index c1ec632f658d0e13d723f1e17a6af8963c78a4a1..c359d7c1de2a233cdb26db8ec9486726c21726a6 100644 --- a/briar-android/res/layout/transports_list.xml +++ b/briar-android/res/layout/transports_list.xml @@ -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" diff --git a/briar-android/res/menu/contact_actions.xml b/briar-android/res/menu/contact_actions.xml new file mode 100644 index 0000000000000000000000000000000000000000..3cce37ad83fcdac5083f7620d935e4184ce66038 --- /dev/null +++ b/briar-android/res/menu/contact_actions.xml @@ -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> \ No newline at end of file diff --git a/briar-android/res/menu/contact_list_actions.xml b/briar-android/res/menu/contact_list_actions.xml new file mode 100644 index 0000000000000000000000000000000000000000..718200a659598ace0bc040e4b034b05c2885cd8d --- /dev/null +++ b/briar-android/res/menu/contact_list_actions.xml @@ -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> \ No newline at end of file diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml index fe43fd7c254ebda72225d15a2c9f0c46dbbb2fd3..0a7c7b1f0e5f97faa642e5295c5634c8dd28b43a 100644 --- a/briar-android/res/values/strings.xml +++ b/briar-android/res/values/strings.xml @@ -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> diff --git a/briar-android/res/values/styles.xml b/briar-android/res/values/styles.xml index f40b8b54192edabca573f826520f778d41b3e5fa..0e0c2c0b94c661f7ab66b33956cc4621dd1587fa 100644 --- a/briar-android/res/values/styles.xml +++ b/briar-android/res/values/styles.xml @@ -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> \ No newline at end of file diff --git a/briar-android/src/org/briarproject/android/contact/ContactListActivity.java b/briar-android/src/org/briarproject/android/contact/ContactListActivity.java index fff550fc46bdb88af5a84902795bfb7be2de9401..1edbd2d2a08f86a59102b9c1bc9b9ebcd6a59441 100644 --- a/briar-android/src/org/briarproject/android/contact/ContactListActivity.java +++ b/briar-android/src/org/briarproject/android/contact/ContactListActivity.java @@ -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 progress bar while the list is loading - loading = new ListLoadingProgressBar(this); - layout.addView(loading); - - layout.addView(new HorizontalBorder(this)); + // Show a notice when there are no contacts + empty = (TextView) findViewById(R.id.emptyView); - 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); + // Show a progress bar while the list is loading + loading = (ProgressBar) findViewById(R.id.progressBar); + loading.setVisibility(VISIBLE); + } - 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() { - 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() { + private void displayContacts(final List<ContactListItem> contacts) { runOnUiThread(new Runnable() { public void run() { - if (adapter.isEmpty()) empty.setVisibility(VISIBLE); - else list.setVisibility(VISIBLE); + if(contacts.size() > 0) { + list.setVisibility(VISIBLE); + empty.setVisibility(GONE); + } else { + list.setVisibility(GONE); + empty.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); - } + adapter.addAll(contacts); } }); } @@ -314,7 +227,7 @@ EventListener { final Collection<PrivateMessageHeader> headers) { runOnUiThread(new Runnable() { public void run() { - ContactListItem item = findItem(c); + ContactListItem item = adapter.findItem(c); if (item != null) { item.setHeaders(headers); adapter.notifyDataSetChanged(); @@ -326,10 +239,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,7 +255,7 @@ EventListener { private void setConnected(final ContactId c, final boolean connected) { runOnUiThread(new Runnable() { public void run() { - ContactListItem item = findItem(c); + ContactListItem item = adapter.findItem(c); if (item != null) { item.setConnected(connected); adapter.notifyDataSetChanged(); diff --git a/briar-android/src/org/briarproject/android/contact/ContactListAdapter.java b/briar-android/src/org/briarproject/android/contact/ContactListAdapter.java index 1acb52435dcd49487f0c7e5990af5f87f5d4927b..7e511ea9a431c4e914bd4f8e88d5a245f78cb140 100644 --- a/briar-android/src/org/briarproject/android/contact/ContactListAdapter.java +++ b/briar-android/src/org/briarproject/android/contact/ContactListAdapter.java @@ -1,80 +1,194 @@ 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; + +import java.util.List; + +public class ContactListAdapter + extends RecyclerView.Adapter<ContactListAdapter.ContactHolder> { - private final int pad; + private SortedList<ContactListItem> contacts = + new SortedList<ContactListItem>(ContactListItem.class, + new SortedList.Callback<ContactListItem>() { + @Override + public void onInserted(int position, int count) { + notifyItemRangeInserted(position, count); + } - ContactListAdapter(Context ctx) { - super(ctx, android.R.layout.simple_expandable_list_item_1, - new ArrayList<ContactListItem>()); - pad = LayoutUtils.getPadding(ctx); + @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) { + return (int) (c1.getTimestamp() - + c2.getTimestamp()); + } + + @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); + if (unread > 0) { + ui.layout.setBackgroundColor( + res.getColor(R.color.unread_background)); + } + + 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) name.setText(contactName + " (" + unread + ")"); - else name.setText(contactName); - layout.addView(name); + if (unread > 0) { + ui.name.setText(contactName + " (" + unread + ")"); + } else { + ui.name.setText(contactName); + } 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); + ui.date.setText(R.string.no_private_messages); } 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); + ui.date.setText( + DateUtils.getRelativeTimeSpanString(ctx, timestamp)); } - return layout; + 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 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 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); + } } } diff --git a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java index ff90470feaaff73345443e50375886a640272853..98dcd179f828d0e5a40e8681fda3916076be9d17 100644 --- a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java +++ b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java @@ -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(); + } + }); + } + }