diff --git a/AndroidManifest.xml b/AndroidManifest.xml index d7361e26..27a0e6dc 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -29,6 +29,9 @@ + diff --git a/res/drawable-hdpi/ic_social_add_person.png b/res/drawable-hdpi/ic_social_add_person.png new file mode 100644 index 00000000..d22a3ea9 Binary files /dev/null and b/res/drawable-hdpi/ic_social_add_person.png differ diff --git a/res/drawable-mdpi/ic_social_add_person.png b/res/drawable-mdpi/ic_social_add_person.png new file mode 100644 index 00000000..9d24b732 Binary files /dev/null and b/res/drawable-mdpi/ic_social_add_person.png differ diff --git a/res/drawable-xhdpi/ic_social_add_person.png b/res/drawable-xhdpi/ic_social_add_person.png new file mode 100644 index 00000000..08d874d2 Binary files /dev/null and b/res/drawable-xhdpi/ic_social_add_person.png differ diff --git a/res/layout/listitem_contact.xml b/res/layout/listitem_contact.xml new file mode 100644 index 00000000..33e7c62e --- /dev/null +++ b/res/layout/listitem_contact.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/res/menu/address_book_list.xml b/res/menu/address_book_list.xml new file mode 100644 index 00000000..86410626 --- /dev/null +++ b/res/menu/address_book_list.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/res/menu/main.xml b/res/menu/main.xml index 3c656f89..5e3fd0d8 100644 --- a/res/menu/main.xml +++ b/res/menu/main.xml @@ -1,6 +1,12 @@ + + I2P-Bote New email Send email + Address book Settings %s selected @@ -39,6 +40,9 @@ Compose email Email queued for sending + Address book is empty + New contact + General General settings and default identity settings Auto-check mail diff --git a/src/i2p/bote/android/EmailListActivity.java b/src/i2p/bote/android/EmailListActivity.java index 2a9fa546..1e3a6f4a 100644 --- a/src/i2p/bote/android/EmailListActivity.java +++ b/src/i2p/bote/android/EmailListActivity.java @@ -2,6 +2,7 @@ package i2p.bote.android; import net.i2p.client.I2PClient; import i2p.bote.I2PBote; +import i2p.bote.android.addressbook.AddressBookActivity; import i2p.bote.android.config.SettingsActivity; import i2p.bote.android.util.MoveToDialogFragment; import i2p.bote.folder.EmailFolder; @@ -163,6 +164,11 @@ public class EmailListActivity extends ActionBarActivity implements } switch (item.getItemId()) { + case R.id.action_address_book: + Intent ai = new Intent(this, AddressBookActivity.class); + startActivity(ai); + return true; + case R.id.action_settings: Intent si = new Intent(this, SettingsActivity.class); startActivity(si); diff --git a/src/i2p/bote/android/addressbook/AddressBookActivity.java b/src/i2p/bote/android/addressbook/AddressBookActivity.java new file mode 100644 index 00000000..c83dfdaa --- /dev/null +++ b/src/i2p/bote/android/addressbook/AddressBookActivity.java @@ -0,0 +1,27 @@ +package i2p.bote.android.addressbook; + +import i2p.bote.packet.dht.Contact; +import android.os.Bundle; +import android.support.v7.app.ActionBarActivity; + +public class AddressBookActivity extends ActionBarActivity implements + AddressBookFragment.OnContactSelectedListener { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Enable ActionBar app icon to behave as action to go back + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + if (savedInstanceState == null) { + AddressBookFragment f = new AddressBookFragment(); + getSupportFragmentManager().beginTransaction() + .add(android.R.id.content, f).commit(); + } + } + + @Override + public void onContactSelected(Contact contact) { + // TODO + } +} diff --git a/src/i2p/bote/android/addressbook/AddressBookFragment.java b/src/i2p/bote/android/addressbook/AddressBookFragment.java new file mode 100644 index 00000000..f73519a4 --- /dev/null +++ b/src/i2p/bote/android/addressbook/AddressBookFragment.java @@ -0,0 +1,139 @@ +package i2p.bote.android.addressbook; + +import i2p.bote.I2PBote; +import i2p.bote.android.R; +import i2p.bote.android.util.BetterAsyncTaskLoader; +import i2p.bote.fileencryption.PasswordException; +import i2p.bote.packet.dht.Contact; + +import java.util.SortedSet; + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.support.v4.app.ListFragment; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.Loader; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.ListView; + +public class AddressBookFragment extends ListFragment implements + LoaderManager.LoaderCallbacks> { + OnContactSelectedListener mCallback; + private ContactAdapter mAdapter; + + // Container Activity must implement this interface + public interface OnContactSelectedListener { + public void onContactSelected(Contact contact); + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + + // This makes sure that the container activity has implemented + // the callback interface. If not, it throws an exception + try { + mCallback = (OnContactSelectedListener) activity; + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + + " must implement OnContactSelectedListener"); + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + mAdapter = new ContactAdapter(getActivity()); + + setListAdapter(mAdapter); + + setListShown(false); + setEmptyText(getResources().getString( + R.string.address_book_empty)); + getLoaderManager().initLoader(0, null, this); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.address_book_list, menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_new_contact: + // TODO + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public void onListItemClick(ListView parent, View view, int pos, long id) { + mCallback.onContactSelected(mAdapter.getItem(pos)); + } + + // LoaderManager.LoaderCallbacks> + + public Loader> onCreateLoader(int id, Bundle args) { + return new AddressBookLoader(getActivity()); + } + + private static class AddressBookLoader extends BetterAsyncTaskLoader> { + public AddressBookLoader(Context context) { + super(context); + } + + @Override + public SortedSet loadInBackground() { + SortedSet contacts = null; + try { + contacts = I2PBote.getInstance().getAddressBook().getAll(); + } catch (PasswordException e) { + // TODO handle, but should not get here + e.printStackTrace(); + } + return contacts; + } + + @Override + protected void onStartMonitoring() { + } + + @Override + protected void onStopMonitoring() { + } + + @Override + protected void releaseResources(SortedSet data) { + } + } + + @Override + public void onLoadFinished(Loader> loader, + SortedSet data) { + mAdapter.setData(data); + + if (isResumed()) { + setListShown(true); + } else { + setListShownNoAnimation(true); + } + } + + @Override + public void onLoaderReset(Loader> loader) { + mAdapter.setData(null); + } +} diff --git a/src/i2p/bote/android/addressbook/ContactAdapter.java b/src/i2p/bote/android/addressbook/ContactAdapter.java new file mode 100644 index 00000000..b0df711a --- /dev/null +++ b/src/i2p/bote/android/addressbook/ContactAdapter.java @@ -0,0 +1,50 @@ +package i2p.bote.android.addressbook; + +import i2p.bote.android.R; +import i2p.bote.android.util.BoteHelper; +import i2p.bote.packet.dht.Contact; + +import java.util.SortedSet; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +public class ContactAdapter extends ArrayAdapter { + private final LayoutInflater mInflater; + + public ContactAdapter(Context context) { + super(context, android.R.layout.simple_list_item_2); + mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + } + + public void setData(SortedSet contacts) { + clear(); + if (contacts != null) { + for (Contact contact : contacts) { + add(contact); + } + } + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View v = mInflater.inflate(R.layout.listitem_contact, parent, false); + Contact contact = getItem(position); + + ImageView picture = (ImageView) v.findViewById(R.id.contact_picture); + TextView name = (TextView) v.findViewById(R.id.contact_name); + + String pic = contact.getPictureBase64(); + if (pic != null) + picture.setImageBitmap(BoteHelper.decodePicture(pic)); + + name.setText(contact.getName()); + + return v; + } +} diff --git a/src/i2p/bote/android/util/BoteHelper.java b/src/i2p/bote/android/util/BoteHelper.java index a363c4be..a97936c5 100644 --- a/src/i2p/bote/android/util/BoteHelper.java +++ b/src/i2p/bote/android/util/BoteHelper.java @@ -91,8 +91,7 @@ public class BoteHelper extends GeneralHelper { // Address is in address book String pic = c.getPictureBase64(); if (pic != null) { - byte[] decodedPic = Base64.decode(pic, Base64.DEFAULT); - return BitmapFactory.decodeByteArray(decodedPic, 0, decodedPic.length); + return decodePicture(pic); } } else { // Address is an identity @@ -100,8 +99,7 @@ public class BoteHelper extends GeneralHelper { if (i != null) { String pic = i.getPictureBase64(); if (pic != null) { - byte[] decodedPic = Base64.decode(pic, Base64.DEFAULT); - return BitmapFactory.decodeByteArray(decodedPic, 0, decodedPic.length); + return decodePicture(pic); } } } @@ -110,4 +108,9 @@ public class BoteHelper extends GeneralHelper { // Address not found anywhere, or found and has no picture return null; } + + public static Bitmap decodePicture(String picB64) { + byte[] decodedPic = Base64.decode(picB64, Base64.DEFAULT); + return BitmapFactory.decodeByteArray(decodedPic, 0, decodedPic.length); + } }