Enable users to filter folders by identity

This commit is contained in:
str4d
2015-06-01 13:29:09 +00:00
parent 076eaa0375
commit 139d977a34
6 changed files with 204 additions and 17 deletions

View File

@@ -3,6 +3,9 @@ package i2p.bote.android;
public class Constants { public class Constants {
public static final String ANDROID_LOG_TAG = "I2P-Bote"; public static final String ANDROID_LOG_TAG = "I2P-Bote";
public static final String SHARED_PREFS = "i2p.bote";
public static final String PREF_SELECTED_IDENTITY = "selectedIdentity";
public static final String EMAILDEST_SCHEME = "bote"; public static final String EMAILDEST_SCHEME = "bote";
public static final String NDEF_DOMAIN = "i2p.bote"; public static final String NDEF_DOMAIN = "i2p.bote";

View File

@@ -23,12 +23,19 @@ import android.widget.AdapterView;
import com.mikepenz.materialdrawer.Drawer; import com.mikepenz.materialdrawer.Drawer;
import com.mikepenz.materialdrawer.DrawerBuilder; import com.mikepenz.materialdrawer.DrawerBuilder;
import com.mikepenz.materialdrawer.accountswitcher.AccountHeader;
import com.mikepenz.materialdrawer.accountswitcher.AccountHeaderBuilder;
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem; import com.mikepenz.materialdrawer.model.PrimaryDrawerItem;
import com.mikepenz.materialdrawer.model.ProfileDrawerItem;
import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
import com.mikepenz.materialdrawer.model.interfaces.IProfile;
import net.i2p.android.ui.I2PAndroidHelper; import net.i2p.android.ui.I2PAndroidHelper;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import i2p.bote.I2PBote; import i2p.bote.I2PBote;
@@ -42,6 +49,7 @@ import i2p.bote.android.service.Init.RouterChoice;
import i2p.bote.android.util.BetterAsyncTaskLoader; import i2p.bote.android.util.BetterAsyncTaskLoader;
import i2p.bote.android.util.BoteHelper; import i2p.bote.android.util.BoteHelper;
import i2p.bote.android.util.MoveToDialogFragment; import i2p.bote.android.util.MoveToDialogFragment;
import i2p.bote.email.EmailIdentity;
import i2p.bote.fileencryption.PasswordCacheListener; import i2p.bote.fileencryption.PasswordCacheListener;
import i2p.bote.fileencryption.PasswordException; import i2p.bote.fileencryption.PasswordException;
import i2p.bote.folder.EmailFolder; import i2p.bote.folder.EmailFolder;
@@ -61,10 +69,10 @@ public class EmailListActivity extends BoteActivityBase implements
/** /**
* Navigation drawer variables * Navigation drawer variables
*/ */
private AccountHeader mAccountHeader;
private Drawer mDrawer; private Drawer mDrawer;
private int mSelected; private int mSelected;
private static final String SHARED_PREFS = "i2p.bote";
private static final String PREF_FIRST_START = "firstStart"; private static final String PREF_FIRST_START = "firstStart";
private static final int SHOW_INTRODUCTION = 1; private static final int SHOW_INTRODUCTION = 1;
@@ -73,6 +81,9 @@ public class EmailListActivity extends BoteActivityBase implements
private static final int ID_ADDRESS_BOOK = 1; private static final int ID_ADDRESS_BOOK = 1;
private static final int ID_NET_STATUS = 2; private static final int ID_NET_STATUS = 2;
private static final int LOADER_IDENTITIES = 0;
private static final int LOADER_DRAWER_FOLDERS = 1;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@@ -84,7 +95,21 @@ public class EmailListActivity extends BoteActivityBase implements
// Initialize variables // Initialize variables
mHelper = new I2PAndroidHelper(this); mHelper = new I2PAndroidHelper(this);
mSharedPrefs = getSharedPreferences(SHARED_PREFS, 0); mSharedPrefs = getSharedPreferences(Constants.SHARED_PREFS, 0);
mAccountHeader = new AccountHeaderBuilder()
.withActivity(this)
.withHeaderBackground(R.drawable.drawer_header_background)
.withOnAccountHeaderListener(new AccountHeader.OnAccountHeaderListener() {
@Override
public boolean onProfileChanged(View view, IProfile profile, boolean currentProfile) {
if (!currentProfile)
identitySelected(profile);
return false;
}
})
.withSavedInstance(savedInstanceState)
.build();
IDrawerItem addressBook = new PrimaryDrawerItem() IDrawerItem addressBook = new PrimaryDrawerItem()
.withIdentifier(ID_ADDRESS_BOOK) .withIdentifier(ID_ADDRESS_BOOK)
@@ -109,6 +134,7 @@ public class EmailListActivity extends BoteActivityBase implements
.withToolbar(toolbar) .withToolbar(toolbar)
.withDrawerWidthPx(drawerWidth) .withDrawerWidthPx(drawerWidth)
.withShowDrawerOnFirstLaunch(true) .withShowDrawerOnFirstLaunch(true)
.withAccountHeader(mAccountHeader)
.addStickyDrawerItems(addressBook, networkStatus) .addStickyDrawerItems(addressBook, networkStatus)
.withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() { .withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
@Override @Override
@@ -160,6 +186,7 @@ public class EmailListActivity extends BoteActivityBase implements
@Override @Override
public void onSaveInstanceState(Bundle outState) { public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
mAccountHeader.saveInstanceState(outState);
mDrawer.saveInstanceState(outState); mDrawer.saveInstanceState(outState);
} }
@@ -193,7 +220,16 @@ public class EmailListActivity extends BoteActivityBase implements
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
getSupportLoaderManager().initLoader(0, null, new DrawerFolderLoaderCallbacks());
if (I2PBote.getInstance().isPasswordRequired()) {
// Ensure any existing data is destroyed.
getSupportLoaderManager().destroyLoader(LOADER_IDENTITIES);
} else {
// Password is cached, or not set.
getSupportLoaderManager().initLoader(LOADER_IDENTITIES, null, new IdentityLoaderCallbacks());
}
getSupportLoaderManager().initLoader(LOADER_DRAWER_FOLDERS, null, new DrawerFolderLoaderCallbacks());
} }
@Override @Override
@@ -287,6 +323,16 @@ public class EmailListActivity extends BoteActivityBase implements
.withSelectedIconColorRes(R.color.primary); .withSelectedIconColorRes(R.color.primary);
} }
private void identitySelected(IProfile profile) {
EmailIdentity identity = (EmailIdentity) ((ProfileDrawerItem) profile).getTag();
mSharedPrefs.edit()
.putString(Constants.PREF_SELECTED_IDENTITY, identity.getKey())
.apply();
EmailListFragment f = (EmailListFragment) getSupportFragmentManager()
.findFragmentById(R.id.list_fragment);
f.onIdentitySelected();
}
private void drawerFolderSelected(EmailFolder folder, boolean alreadySelected) { private void drawerFolderSelected(EmailFolder folder, boolean alreadySelected) {
if (!alreadySelected) { if (!alreadySelected) {
// Create the new fragment // Create the new fragment
@@ -351,6 +397,87 @@ public class EmailListActivity extends BoteActivityBase implements
// Loaders // Loaders
// //
private class IdentityLoaderCallbacks implements LoaderManager.LoaderCallbacks<ArrayList<IProfile>> {
@Override
public Loader<ArrayList<IProfile>> onCreateLoader(int id, Bundle args) {
return new DrawerIdentityLoader(EmailListActivity.this);
}
@Override
public void onLoadFinished(Loader<ArrayList<IProfile>> loader, ArrayList<IProfile> data) {
mAccountHeader.setProfiles(data);
String selectedIdentity = mSharedPrefs.getString(Constants.PREF_SELECTED_IDENTITY, null);
if (selectedIdentity != null) {
for (IProfile profile : data) {
EmailIdentity identity = (EmailIdentity) ((ProfileDrawerItem) profile).getTag();
if (selectedIdentity.equals(identity.getKey())) {
mAccountHeader.setActiveProfile(profile, true);
break;
}
}
} else // AccountHeader selects the first one by default
identitySelected(data.get(0));
}
@Override
public void onLoaderReset(Loader<ArrayList<IProfile>> loader) {
mAccountHeader.clear();
}
}
private static class DrawerIdentityLoader extends BetterAsyncTaskLoader<ArrayList<IProfile>> {
private int identiconSize;
public DrawerIdentityLoader(Context context) {
super(context);
identiconSize = context.getResources().getDimensionPixelSize(
com.mikepenz.materialdrawer.R.dimen.material_drawer_item_profile_icon);
}
@Override
public ArrayList<IProfile> loadInBackground() {
ArrayList<IProfile> profiles = new ArrayList<>();
try {
Collection<EmailIdentity> identities = I2PBote.getInstance().getIdentities().getAll();
for (EmailIdentity identity : identities) {
profiles.add(getIdentityDrawerItem(identity));
}
} catch (PasswordException e) {
// TODO handle, but should not get here
e.printStackTrace();
} catch (GeneralSecurityException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return profiles;
}
private IProfile getIdentityDrawerItem(EmailIdentity identity) {
return new ProfileDrawerItem()
.withIdentifier(identity.hashCode())
.withTag(identity)
.withName(identity.getPublicName())
.withEmail(identity.getDescription())
.withIcon(BoteHelper.getIdentityPicture(identity, identiconSize, identiconSize));
}
@Override
protected void onStartMonitoring() {
}
@Override
protected void onStopMonitoring() {
}
@Override
protected void releaseResources(ArrayList<IProfile> data) {
}
}
private class DrawerFolderLoaderCallbacks implements LoaderManager.LoaderCallbacks<List<IDrawerItem>> { private class DrawerFolderLoaderCallbacks implements LoaderManager.LoaderCallbacks<List<IDrawerItem>> {
@Override @Override
public Loader<List<IDrawerItem>> onCreateLoader(int id, Bundle args) { public Loader<List<IDrawerItem>> onCreateLoader(int id, Bundle args) {
@@ -405,6 +532,7 @@ public class EmailListActivity extends BoteActivityBase implements
.withName(BoteHelper.getFolderDisplayName(getContext(), folder)); .withName(BoteHelper.getFolderDisplayName(getContext(), folder));
try { try {
// TODO change this when per-identity new emails can be determined
int numNew = folder.getNumNewEmails(); int numNew = folder.getNumNewEmails();
if (numNew > 0) if (numNew > 0)
item.withBadge("" + numNew); item.withBadge("" + numNew);
@@ -484,14 +612,20 @@ public class EmailListActivity extends BoteActivityBase implements
@Override @Override
public void passwordProvided() { public void passwordProvided() {
// Trigger the loader to show the drawer badges // Password is cached, or not set.
getSupportLoaderManager().restartLoader(0, null, new DrawerFolderLoaderCallbacks()); getSupportLoaderManager().restartLoader(LOADER_IDENTITIES, null, new IdentityLoaderCallbacks());
// Trigger the drawer folder loader to show the drawer badges
getSupportLoaderManager().restartLoader(LOADER_DRAWER_FOLDERS, null, new DrawerFolderLoaderCallbacks());
} }
@Override @Override
public void passwordCleared() { public void passwordCleared() {
// Trigger the loader to hide the drawer badges if (mAccountHeader.isSelectionListShown())
getSupportLoaderManager().restartLoader(0, null, new DrawerFolderLoaderCallbacks()); mAccountHeader.toggleSelectionList(this);
// Ensure any existing data is destroyed.
getSupportLoaderManager().destroyLoader(LOADER_IDENTITIES);
// Trigger the drawer folder loader to hide the drawer badges
getSupportLoaderManager().restartLoader(LOADER_DRAWER_FOLDERS, null, new DrawerFolderLoaderCallbacks());
} }
// NetworkStatusListener // NetworkStatusListener

View File

@@ -29,8 +29,10 @@ import net.i2p.util.Log;
import java.io.IOException; import java.io.IOException;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.mail.Address;
import javax.mail.Flags.Flag; import javax.mail.Flags.Flag;
import javax.mail.MessagingException; import javax.mail.MessagingException;
@@ -370,6 +372,12 @@ public class EmailListFragment extends AuthenticatedFragment implements
} }
} }
// Called by EmailListActivity.onIdentitySelected()
public void onIdentitySelected() {
getLoaderManager().restartLoader(EMAIL_LIST_LOADER, null, this);
}
// Called by EmailListActivity.onFolderSelected() // Called by EmailListActivity.onFolderSelected()
public void onFolderSelected(EmailFolder newFolder) { public void onFolderSelected(EmailFolder newFolder) {
@@ -384,26 +392,61 @@ public class EmailListFragment extends AuthenticatedFragment implements
// LoaderManager.LoaderCallbacks<List<Email>> // LoaderManager.LoaderCallbacks<List<Email>>
public Loader<List<Email>> onCreateLoader(int id, Bundle args) { public Loader<List<Email>> onCreateLoader(int id, Bundle args) {
return new EmailListLoader(getActivity(), mFolder); return new EmailListLoader(getActivity(), mFolder,
getActivity().getSharedPreferences(Constants.SHARED_PREFS, 0)
.getString(Constants.PREF_SELECTED_IDENTITY, null));
} }
private static class EmailListLoader extends BetterAsyncTaskLoader<List<Email>> implements private static class EmailListLoader extends BetterAsyncTaskLoader<List<Email>> implements
FolderListener { FolderListener {
private EmailFolder mFolder; private EmailFolder mFolder;
private String mSelectedIdentityKey;
public EmailListLoader(Context context, EmailFolder folder) { public EmailListLoader(Context context, EmailFolder folder, String selectedIdentityKey) {
super(context); super(context);
mFolder = folder; mFolder = folder;
mSelectedIdentityKey = selectedIdentityKey;
} }
@Override @Override
public List<Email> loadInBackground() { public List<Email> loadInBackground() {
List<Email> emails = null; List<Email> emails = null;
try { try {
emails = BoteHelper.getEmails(mFolder, null, true); List<Email> allEmails = BoteHelper.getEmails(mFolder, null, true);
if (mSelectedIdentityKey != null) {
emails = new ArrayList<>();
for (Email email : allEmails) {
boolean add = false;
if (BoteHelper.isSentEmail(email)) {
String senderDest = BoteHelper.extractEmailDestination(email.getOneFromAddress());
if (mSelectedIdentityKey.equals(senderDest))
add = true;
} else {
for (Address recipient : email.getAllRecipients()) {
String recipientDest = BoteHelper.extractEmailDestination(recipient.toString());
if (mSelectedIdentityKey.equals(recipientDest)) {
add = true;
break;
}
}
}
if (add)
emails.add(email);
}
} else
emails = allEmails;
} catch (PasswordException pe) { } catch (PasswordException pe) {
// XXX: Should not get here. // XXX: Should not get here.
} catch (MessagingException e) {
e.printStackTrace();
} catch (GeneralSecurityException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} }
return emails; return emails;
} }

View File

@@ -89,13 +89,8 @@ public class IdentityAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
final IdentityViewHolder cvh = (IdentityViewHolder) holder; final IdentityViewHolder cvh = (IdentityViewHolder) holder;
EmailIdentity identity = mIdentities.get(position); EmailIdentity identity = mIdentities.get(position);
String pic = identity.getPictureBase64();
if (pic != null && !pic.isEmpty())
cvh.mPicture.setImageBitmap(BoteHelper.decodePicture(pic));
else {
ViewGroup.LayoutParams lp = cvh.mPicture.getLayoutParams(); ViewGroup.LayoutParams lp = cvh.mPicture.getLayoutParams();
cvh.mPicture.setImageBitmap(BoteHelper.getIdenticonForAddress(identity.getKey(), lp.width, lp.height)); cvh.mPicture.setImageBitmap(BoteHelper.getIdentityPicture(identity, lp.width, lp.height));
}
cvh.mName.setText(identity.getPublicName()); cvh.mName.setText(identity.getPublicName());

View File

@@ -208,6 +208,14 @@ public class BoteHelper extends GeneralHelper {
return bitmap; return bitmap;
} }
public static Bitmap getIdentityPicture(EmailIdentity identity, int identiconWidth, int identiconHeight) {
String pic = identity.getPictureBase64();
if (pic != null && !pic.isEmpty())
return BoteHelper.decodePicture(pic);
else
return BoteHelper.getIdenticonForAddress(identity.getKey(), identiconWidth, identiconHeight);
}
private static final String PROPERTY_SENT = "sent"; private static final String PROPERTY_SENT = "sent";
public static void setEmailSent(Email email, boolean isSent) { public static void setEmailSent(Email email, boolean isSent) {
email.getMetadata().setProperty(PROPERTY_SENT, isSent ? "true" : "false"); email.getMetadata().setProperty(PROPERTY_SENT, isSent ? "true" : "false");

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/primary"/>
</shape>