From bf52d2f3de4e2eb78026cefe99b647dbf399ddeb Mon Sep 17 00:00:00 2001
From: Christian Basler
Date: Thu, 29 Jun 2017 23:29:56 +0200
Subject: [PATCH] Asynchronously load contacts to improve responsiveness
---
.../apps/abit/AddressDetailFragment.java | 3 -
.../dissem/apps/abit/AddressListFragment.java | 120 ++++++------
.../apps/abit/ImportIdentitiesFragment.java | 26 ++-
.../ch/dissem/apps/abit/MainActivity.java | 13 +-
.../repository/AndroidAddressRepository.java | 174 ++++++++++++------
.../repository/AndroidMessageRepository.java | 34 ++--
.../abit/repository/AndroidNodeRegistry.java | 8 +-
.../AndroidProofOfWorkRepository.java | 19 +-
.../apps/abit/service/BitmessageService.java | 2 +-
.../dissem/apps/abit/service/Singleton.java | 8 +-
.../abit/synchronization/SyncAdapter.java | 4 +-
.../ch/dissem/apps/abit/util/Drawables.java | 8 +-
.../ch/dissem/apps/abit/util/UuidUtils.java | 6 +-
.../main/res/drawable/avatar_placeholder.xml | 12 ++
app/src/main/res/layout/subscription_row.xml | 2 +-
app/src/main/res/values/strings.xml | 2 +-
build.gradle | 2 +-
17 files changed, 261 insertions(+), 182 deletions(-)
create mode 100644 app/src/main/res/drawable/avatar_placeholder.xml
diff --git a/app/src/main/java/ch/dissem/apps/abit/AddressDetailFragment.java b/app/src/main/java/ch/dissem/apps/abit/AddressDetailFragment.java
index 9dec757..791e1b2 100644
--- a/app/src/main/java/ch/dissem/apps/abit/AddressDetailFragment.java
+++ b/app/src/main/java/ch/dissem/apps/abit/AddressDetailFragment.java
@@ -70,9 +70,6 @@ public class AddressDetailFragment extends Fragment {
super.onCreate(savedInstanceState);
if (getArguments().containsKey(ARG_ITEM)) {
- // Load the dummy content specified by the fragment
- // arguments. In a real-world scenario, use a Loader
- // to load content from a content provider.
item = (BitmessageAddress) getArguments().getSerializable(ARG_ITEM);
}
setHasOptionsMenu(true);
diff --git a/app/src/main/java/ch/dissem/apps/abit/AddressListFragment.java b/app/src/main/java/ch/dissem/apps/abit/AddressListFragment.java
index 93e1f2e..9633cbe 100644
--- a/app/src/main/java/ch/dissem/apps/abit/AddressListFragment.java
+++ b/app/src/main/java/ch/dissem/apps/abit/AddressListFragment.java
@@ -19,6 +19,7 @@ package ch.dissem.apps.abit;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@@ -32,11 +33,11 @@ import android.widget.TextView;
import com.google.zxing.integration.android.IntentIntegrator;
-import java.util.Collections;
-import java.util.Comparator;
+import java.util.LinkedList;
import java.util.List;
import ch.dissem.apps.abit.listener.ActionBarListener;
+import ch.dissem.apps.abit.repository.AndroidAddressRepository;
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.valueobject.Label;
@@ -47,6 +48,46 @@ import io.github.yavski.fabspeeddial.SimpleMenuListenerAdapter;
* Fragment that shows a list of all contacts, the ones we subscribed to first.
*/
public class AddressListFragment extends AbstractItemListFragment {
+ private ArrayAdapter adapter;
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ adapter = new ArrayAdapter(
+ getActivity(),
+ R.layout.subscription_row,
+ R.id.name,
+ new LinkedList()) {
+ @NonNull
+ @Override
+ public View getView(int position, View convertView, @NonNull ViewGroup parent) {
+ ViewHolder v;
+ if (convertView == null) {
+ LayoutInflater inflater = LayoutInflater.from(getContext());
+ convertView = inflater.inflate(R.layout.subscription_row, parent, false);
+ v = new ViewHolder();
+ v.ctx = getContext();
+ v.avatar = (ImageView) convertView.findViewById(R.id.avatar);
+ v.name = (TextView) convertView.findViewById(R.id.name);
+ v.streamNumber = (TextView) convertView.findViewById(R.id.stream_number);
+ v.subscribed = convertView.findViewById(R.id.subscribed);
+ convertView.setTag(v);
+ } else {
+ v = (ViewHolder) convertView.getTag();
+ }
+ BitmessageAddress item = getItem(position);
+ assert item != null;
+ v.avatar.setImageDrawable(new Identicon(item));
+ v.name.setText(item.toString());
+ v.streamNumber.setText(v.ctx.getString(R.string.stream_number, item.getStream()));
+ v.subscribed.setVisibility(item.isSubscribed() ? View.VISIBLE : View.INVISIBLE);
+ return convertView;
+ }
+ };
+ setListAdapter(adapter);
+ }
+
@Override
public void onResume() {
super.onResume();
@@ -55,61 +96,26 @@ public class AddressListFragment extends AbstractItemListFragment addresses = Singleton.getAddressRepository(getContext())
- .getContacts();
- Collections.sort(addresses, new Comparator() {
+ adapter.clear();
+ final AndroidAddressRepository addressRepo = Singleton.getAddressRepository(getContext());
+ new AsyncTask() {
@Override
- public int compare(BitmessageAddress lhs, BitmessageAddress rhs) {
- // Yields the following order:
- // * Subscribed addresses come first
- // * Addresses with Aliases (alphabetically)
- // * Addresses (alphabetically)
- if (lhs.isSubscribed() == rhs.isSubscribed()) {
- if (lhs.getAlias() != null) {
- if (rhs.getAlias() != null) {
- return lhs.getAlias().compareTo(rhs.getAlias());
- } else {
- return -1;
- }
- } else if (rhs.getAlias() != null) {
- return 1;
- } else {
- return lhs.getAddress().compareTo(rhs.getAddress());
- }
+ protected Void doInBackground(Void... params) {
+ List ids = addressRepo.getContactIds();
+ for (String id : ids) {
+ BitmessageAddress address = addressRepo.getById(id);
+ publishProgress(address);
}
- if (lhs.isSubscribed()) {
- return -1;
- } else {
- return 1;
+ return null;
+ }
+
+ @Override
+ protected void onProgressUpdate(BitmessageAddress... values) {
+ for (BitmessageAddress address : values) {
+ adapter.add(address);
}
}
- });
- setListAdapter(new ArrayAdapter(
- getActivity(),
- android.R.layout.simple_list_item_activated_1,
- android.R.id.text1,
- addresses) {
- @NonNull
- @Override
- public View getView(int position, View convertView, @NonNull ViewGroup parent) {
- if (convertView == null) {
- LayoutInflater inflater = LayoutInflater.from(getContext());
- convertView = inflater.inflate(R.layout.subscription_row, parent, false);
- }
- BitmessageAddress item = getItem(position);
- assert item != null;
- ((ImageView) convertView.findViewById(R.id.avatar)).setImageDrawable(new
- Identicon(item));
- TextView name = (TextView) convertView.findViewById(R.id.name);
- name.setText(item.toString());
- TextView streamNumber = (TextView) convertView.findViewById(R.id.stream_number);
- streamNumber.setText(getContext().getString(R.string.stream_number,
- item.getStream()));
- convertView.findViewById(R.id.subscribed).setVisibility(item.isSubscribed() ?
- View.VISIBLE : View.INVISIBLE);
- return convertView;
- }
- });
+ }.execute();
}
@Override
@@ -163,4 +169,12 @@ public class AddressListFragment extends AbstractItemListFragment getIdentities() {
return find("private_key IS NOT NULL");
}
+ @NonNull
@Override
public List getChans() {
return find("chan = '1'");
}
+ @NonNull
@Override
public List getSubscriptions() {
return find("subscribed = '1'");
}
+ @NonNull
@Override
public List getSubscriptions(long broadcastVersion) {
if (broadcastVersion > 4) {
@@ -108,11 +113,55 @@ public class AndroidAddressRepository implements AddressRepository {
}
}
+ @NonNull
@Override
public List getContacts() {
return find("private_key IS NULL OR chan = '1'");
}
+ /**
+ * Returns the contacts in the following order:
+ *
+ *
Subscribed addresses come first
+ *
Addresses with Aliases (alphabetically)
+ *
Addresses (alphabetically)
+ *
+ *
+ * @return the ordered list of ids (address strings)
+ */
+ @NonNull
+ public List getContactIds() {
+ return findIds(
+ "private_key IS NULL OR chan = '1'",
+ COLUMN_SUBSCRIBED + " DESC, " + COLUMN_ALIAS + " IS NULL, " + COLUMN_ALIAS + ", " + COLUMN_ADDRESS
+ );
+ }
+
+ @NonNull
+ private List findIds(String where, String orderBy) {
+ List result = new LinkedList<>();
+
+ // Define a projection that specifies which columns from the database
+ // you will actually use after this query.
+ String[] projection = {
+ COLUMN_ADDRESS
+ };
+
+ SQLiteDatabase db = sql.getReadableDatabase();
+ try (Cursor c = db.query(
+ TABLE_NAME, projection,
+ where,
+ null, null, null,
+ orderBy
+ )) {
+ while (c.moveToNext()) {
+ result.add(c.getString(c.getColumnIndex(COLUMN_ADDRESS)));
+ }
+ }
+ return result;
+ }
+
+ @NonNull
private List find(String where) {
List result = new LinkedList<>();
@@ -161,8 +210,6 @@ public class AndroidAddressRepository implements AddressRepository {
result.add(address);
}
- } catch (IOException e) {
- LOG.error(e.getMessage(), e);
}
return result;
}
@@ -188,61 +235,55 @@ public class AndroidAddressRepository implements AddressRepository {
}
private void update(BitmessageAddress address) {
- try {
- SQLiteDatabase db = sql.getWritableDatabase();
- // Create a new map of values, where column names are the keys
- ContentValues values = new ContentValues();
- if (address.getAlias() != null) {
- values.put(COLUMN_ALIAS, address.getAlias());
- }
- if (address.getPubkey() != null) {
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- address.getPubkey().writeUnencrypted(out);
- values.put(COLUMN_PUBLIC_KEY, out.toByteArray());
- }
- if (address.getPrivateKey() != null) {
- values.put(COLUMN_PRIVATE_KEY, Encode.bytes(address.getPrivateKey()));
- }
- if (address.isChan()) {
- values.put(COLUMN_CHAN, true);
- }
- values.put(COLUMN_SUBSCRIBED, address.isSubscribed());
+ SQLiteDatabase db = sql.getWritableDatabase();
+ // Create a new map of values, where column names are the keys
+ ContentValues values = new ContentValues();
+ if (address.getAlias() != null) {
+ values.put(COLUMN_ALIAS, address.getAlias());
+ }
+ if (address.getPubkey() != null) {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ address.getPubkey().writeUnencrypted(out);
+ values.put(COLUMN_PUBLIC_KEY, out.toByteArray());
+ }
+ if (address.getPrivateKey() != null) {
+ values.put(COLUMN_PRIVATE_KEY, Encode.bytes(address.getPrivateKey()));
+ }
+ if (address.isChan()) {
+ values.put(COLUMN_CHAN, true);
+ }
+ values.put(COLUMN_SUBSCRIBED, address.isSubscribed());
- int update = db.update(TABLE_NAME, values, "address=?",
- new String[]{address.getAddress()});
- if (update < 0) {
- LOG.error("Could not update address " + address);
- }
- } catch (IOException e) {
- LOG.error(e.getMessage(), e);
+ int update = db.update(TABLE_NAME, values, "address=?",
+ new String[]{address.getAddress()});
+ if (update < 0) {
+ LOG.error("Could not update address " + address);
}
}
private void insert(BitmessageAddress address) {
- try {
- SQLiteDatabase db = sql.getWritableDatabase();
- // Create a new map of values, where column names are the keys
- ContentValues values = new ContentValues();
- values.put(COLUMN_ADDRESS, address.getAddress());
- values.put(COLUMN_VERSION, address.getVersion());
- values.put(COLUMN_ALIAS, address.getAlias());
- if (address.getPubkey() != null) {
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- address.getPubkey().writeUnencrypted(out);
- values.put(COLUMN_PUBLIC_KEY, out.toByteArray());
- } else {
- values.put(COLUMN_PUBLIC_KEY, (byte[]) null);
- }
+ SQLiteDatabase db = sql.getWritableDatabase();
+ // Create a new map of values, where column names are the keys
+ ContentValues values = new ContentValues();
+ values.put(COLUMN_ADDRESS, address.getAddress());
+ values.put(COLUMN_VERSION, address.getVersion());
+ values.put(COLUMN_ALIAS, address.getAlias());
+ if (address.getPubkey() != null) {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ address.getPubkey().writeUnencrypted(out);
+ values.put(COLUMN_PUBLIC_KEY, out.toByteArray());
+ } else {
+ values.put(COLUMN_PUBLIC_KEY, (byte[]) null);
+ }
+ if (address.getPrivateKey() != null) {
values.put(COLUMN_PRIVATE_KEY, Encode.bytes(address.getPrivateKey()));
- values.put(COLUMN_CHAN, address.isChan());
- values.put(COLUMN_SUBSCRIBED, address.isSubscribed());
+ }
+ values.put(COLUMN_CHAN, address.isChan());
+ values.put(COLUMN_SUBSCRIBED, address.isSubscribed());
- long insert = db.insert(TABLE_NAME, null, values);
- if (insert < 0) {
- LOG.error("Could not insert address " + address);
- }
- } catch (IOException e) {
- LOG.error(e.getMessage(), e);
+ long insert = db.insert(TABLE_NAME, null, values);
+ if (insert < 0) {
+ LOG.error("Could not insert address " + address);
}
}
@@ -252,10 +293,21 @@ public class AndroidAddressRepository implements AddressRepository {
db.delete(TABLE_NAME, "address = ?", new String[]{address.getAddress()});
}
+ @NonNull
+ public BitmessageAddress getById(String id) {
+ List result = find("address = '" + id + "'");
+ if (result.size() > 0) {
+ return result.get(0);
+ } else {
+ throw new ApplicationException("Address with id " + id + " not found.");
+ }
+ }
+
+ @NonNull
@Override
public BitmessageAddress getAddress(String address) {
List result = find("address = '" + address + "'");
if (result.size() > 0) return result.get(0);
- return null;
+ return new BitmessageAddress(address);
}
}
diff --git a/app/src/main/java/ch/dissem/apps/abit/repository/AndroidMessageRepository.java b/app/src/main/java/ch/dissem/apps/abit/repository/AndroidMessageRepository.java
index 8d44495..b62e761 100644
--- a/app/src/main/java/ch/dissem/apps/abit/repository/AndroidMessageRepository.java
+++ b/app/src/main/java/ch/dissem/apps/abit/repository/AndroidMessageRepository.java
@@ -22,12 +22,12 @@ import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDatabase;
+import android.support.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
-import java.io.IOException;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
@@ -91,6 +91,7 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
this.context = ctx;
}
+ @NonNull
@Override
public List findMessages(Label label) {
if (label == LABEL_ARCHIVE) {
@@ -100,6 +101,7 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
}
}
+ @NonNull
public List
*/
public class UuidUtils {
+ /**
+ * @param bytes that represent a UUID, or null for a random UUID
+ * @return the UUID from the given bytes, or a random UUID if bytes is null.
+ */
public static UUID asUuid(byte[] bytes) {
if (bytes == null) {
- return null;
+ return UUID.randomUUID();
}
ByteBuffer bb = ByteBuffer.wrap(bytes);
long firstLong = bb.getLong();
diff --git a/app/src/main/res/drawable/avatar_placeholder.xml b/app/src/main/res/drawable/avatar_placeholder.xml
new file mode 100644
index 0000000..9b5bd5d
--- /dev/null
+++ b/app/src/main/res/drawable/avatar_placeholder.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/subscription_row.xml b/app/src/main/res/layout/subscription_row.xml
index 8a40938..83ef228 100644
--- a/app/src/main/res/layout/subscription_row.xml
+++ b/app/src/main/res/layout/subscription_row.xml
@@ -29,7 +29,7 @@
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_margin="16dp"
- android:src="@color/colorAccent"
+ android:src="@drawable/avatar_placeholder"
tools:ignore="ContentDescription"/>
Public key available
Public key not yet availableQR code
- Having more identities will reequire more resources. If you are sure you want to add an identity, please select what exactly you want to do:
+ Having more identities will require more resources. If you are sure you want to add an identity, please select what exactly you want to do:ShareAre you sure you want to delete this identity? You won\'t be able to receive any messages sent to this address and can\'t undo this operation.Are you sure you want to delete this contact?
diff --git a/build.gradle b/build.gradle
index 51bb49b..b06c3d3 100644
--- a/build.gradle
+++ b/build.gradle
@@ -9,7 +9,7 @@ buildscript {
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:2.3.2'
+ classpath 'com.android.tools.build:gradle:2.3.3'
classpath 'com.github.ben-manes:gradle-versions-plugin:0.14.0'
// NOTE: Do not place your application dependencies here; they belong