package i2p.bote.android;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.view.ActionMode;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.Toast;
import com.mikepenz.google_material_typeface_library.GoogleMaterial;
import com.pnikosis.materialishprogress.ProgressWheel;
import net.i2p.I2PAppContext;
import net.i2p.util.Log;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.List;
import javax.mail.Address;
import javax.mail.Flags.Flag;
import javax.mail.MessagingException;
import i2p.bote.I2PBote;
import i2p.bote.android.util.AuthenticatedFragment;
import i2p.bote.android.util.BetterAsyncTaskLoader;
import i2p.bote.android.util.BoteHelper;
import i2p.bote.android.util.MoveToDialogFragment;
import i2p.bote.android.util.MultiSelectionUtil;
import i2p.bote.android.widget.DividerItemDecoration;
import i2p.bote.android.widget.LoadingRecyclerView;
import i2p.bote.android.widget.MultiSwipeRefreshLayout;
import i2p.bote.email.Email;
import i2p.bote.fileencryption.PasswordException;
import i2p.bote.folder.EmailFolder;
import i2p.bote.folder.FolderListener;
public class EmailListFragment extends AuthenticatedFragment implements
LoaderManager.LoaderCallbacks<List<Email>>,
MoveToDialogFragment.MoveToDialogListener,
SwipeRefreshLayout.OnRefreshListener {
public static final String FOLDER_NAME = "folder_name";
private static final int EMAIL_LIST_LOADER = 1;
OnEmailSelectedListener mCallback;
private MultiSwipeRefreshLayout mSwipeRefreshLayout;
private AsyncTask<Void, Void, Void> mCheckingTask;
private LoadingRecyclerView mEmailsList;
private EmailListAdapter mAdapter;
private EmailFolder mFolder;
private ImageButton mNewEmail;
private MenuItem mCheckEmail;
// The Controller which provides CHOICE_MODE_MULTIPLE_MODAL-like functionality
private MultiSelectionUtil.Controller mMultiSelectController;
private ModalChoiceListener mModalChoiceListener;
public static EmailListFragment newInstance(String folderName) {
EmailListFragment f = new EmailListFragment();
Bundle args = new Bundle();
args.putString(FOLDER_NAME, folderName);
f.setArguments(args);
return f;
}
// Container Activity must implement this interface
public interface OnEmailSelectedListener {
public void onEmailSelected(String folderName, String messageId);
}
@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 = (OnEmailSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnEmailSelectedListener");
}
}
@Override
public View onCreateAuthenticatedView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
String folderName = getArguments().getString(FOLDER_NAME);
mFolder = BoteHelper.getMailFolder(folderName);
boolean isInbox = BoteHelper.isInbox(mFolder);
View v = inflater.inflate(
isInbox ? R.layout.fragment_list_emails_with_refresh : R.layout.fragment_list_emails,
container, false);
mEmailsList = (LoadingRecyclerView) v.findViewById(R.id.emails_list);
View empty = v.findViewById(R.id.empty);
ProgressWheel loading = (ProgressWheel) v.findViewById(R.id.loading);
mEmailsList.setLoadingView(empty, loading);
mNewEmail = (ImageButton) v.findViewById(R.id.promoted_action);
mNewEmail.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startNewEmail();
}
});
if (isInbox) {
mSwipeRefreshLayout = (MultiSwipeRefreshLayout) v;
// Set up the MultiSwipeRefreshLayout
mSwipeRefreshLayout.setColorSchemeResources(
R.color.primary, R.color.accent, R.color.primary, R.color.accent);
mSwipeRefreshLayout.setSwipeableChildren(R.id.emails_list);
mSwipeRefreshLayout.setOnRefreshListener(this);
}
return v;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mEmailsList.setHasFixedSize(true);
mEmailsList.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST));
// Use a linear layout manager
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getActivity());
mEmailsList.setLayoutManager(mLayoutManager);
// Set the adapter for the list view
mAdapter = new EmailListAdapter(getActivity(), mFolder.getName(), mCallback);
mEmailsList.setAdapter(mAdapter);
// Attach a MultiSelectionUtil.Controller to the ListView, giving it an instance of
// ModalChoiceListener (see below)
mModalChoiceListener = new ModalChoiceListener();
mMultiSelectController = MultiSelectionUtil
.attachMultiSelectionController(mEmailsList, (AppCompatActivity) getActivity(),
mModalChoiceListener);
// Allow the Controller to restore itself
mMultiSelectController.restoreInstanceState(savedInstanceState);
if (mFolder == null) {
mFolder = I2PBote.getInstance().getInbox();
Toast.makeText(getActivity(), R.string.folder_does_not_exist, Toast.LENGTH_SHORT).show();
}
getActivity().setTitle(
BoteHelper.getFolderDisplayName(getActivity(), mFolder));
}
@Override
public void onStart() {
super.onStart();
if (mSwipeRefreshLayout != null) {
boolean isChecking = I2PBote.getInstance().isCheckingForMail();
mSwipeRefreshLayout.setRefreshing(isChecking);
if (isChecking)
onRefresh();
}
}
@Override
public void onStop() {
super.onStop();
if (mCheckingTask != null) {
mCheckingTask.cancel(true);
mCheckingTask = null;
mSwipeRefreshLayout.setRefreshing(false);
}
}
/**
* Start loading the list of emails from this folder.
* Only called when we have a password cached, or no
* password is required.
*/
protected void onInitializeFragment() {
if (mFolder == null)
return;
if (BoteHelper.isInbox(mFolder)) {
mAdapter.setIncompleteEmails(I2PBote.getInstance().getNumIncompleteEmails());
}
getLoaderManager().initLoader(EMAIL_LIST_LOADER, null, this);
}
protected void onDestroyFragment() {
mAdapter.setIncompleteEmails(0);
getLoaderManager().destroyLoader(EMAIL_LIST_LOADER);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Allow the Controller to save it's instance state so that any checked items are
// stored
if (mMultiSelectController != null)
mMultiSelectController.saveInstanceState(outState);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.email_list, menu);
mCheckEmail = menu.findItem(R.id.action_check_email);
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
boolean passwordRequired = I2PBote.getInstance().isPasswordRequired();
mNewEmail.setVisibility(passwordRequired ? View.GONE : View.VISIBLE);
mCheckEmail.setVisible(mSwipeRefreshLayout != null && !passwordRequired);
if (mSwipeRefreshLayout != null) {
mSwipeRefreshLayout.setEnabled(!passwordRequired);
if (mSwipeRefreshLayout.isRefreshing()) {
mCheckEmail.setTitle(R.string.checking_email);
mCheckEmail.setEnabled(false);
} else {
mCheckEmail.setTitle(R.string.check_email);
mCheckEmail.setEnabled(true);
}
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_check_email:
if (!mSwipeRefreshLayout.isRefreshing()) {
mSwipeRefreshLayout.setRefreshing(true);
onRefresh();
getActivity().supportInvalidateOptionsMenu();
}
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private void startNewEmail() {
Intent nei = new Intent(getActivity(), NewEmailActivity.class);
startActivity(nei);
}
private class ModalChoiceListener implements MultiSelectionUtil.MultiChoiceModeListener {
private boolean areUnread;
@Override
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
int numChecked = mAdapter.getSelectedItemCount();
mode.setTitle(getResources().getString(R.string.items_selected, numChecked));
if (checked && numChecked == 1) { // This is the first checked item
Email email = mAdapter.getEmail(position);
areUnread = email.isUnread();
mode.invalidate();
}
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
// Respond to clicks on the actions in the CAB
switch (item.getItemId()) {
case R.id.action_delete:
List<Integer> toDelete = mAdapter.getSelectedItems();
if (toDelete.size() == 0)
return false;
for (int i = (toDelete.size() - 1); i >= 0; i--) {
Email email = mAdapter.getEmail(toDelete.get(i));
BoteHelper.revokeAttachmentUriPermissions(
getActivity(),
mFolder.getName(),
email);
// The Loader will update mAdapter
I2PBote.getInstance().deleteEmail(mFolder, email.getMessageID());
}
mode.finish();
return true;
case R.id.action_mark_read:
case R.id.action_mark_unread:
List<Integer> selected = mAdapter.getSelectedItems();
for (int i = (selected.size() - 1); i >= 0; i--) {
Email email = mAdapter.getEmail(selected.get(i));
try {
// The Loader will update mAdapter
mFolder.setNew(email, !areUnread);
} catch (PasswordException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (GeneralSecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
areUnread = !areUnread;
mode.invalidate();
return true;
case R.id.action_move_to:
DialogFragment f = MoveToDialogFragment.newInstance(mFolder);
f.show(getFragmentManager(), "moveTo");
return true;
default:
return false;
}
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
// Inflate the menu for the CAB
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.email_list_context, menu);
MenuItem markRead = menu.findItem(R.id.action_mark_read);
MenuItem markUnread = menu.findItem(R.id.action_mark_unread);
MenuItem moveTo = menu.findItem(R.id.action_move_to);
menu.findItem(R.id.action_delete).setIcon(BoteHelper.getMenuIcon(getActivity(), GoogleMaterial.Icon.gmd_delete));
markRead.setIcon(BoteHelper.getMenuIcon(getActivity(), GoogleMaterial.Icon.gmd_drafts));
markUnread.setIcon(BoteHelper.getMenuIcon(getActivity(), GoogleMaterial.Icon.gmd_markunread));
moveTo.setIcon(BoteHelper.getMenuIcon(getActivity(), GoogleMaterial.Icon.gmd_folder));
if (BoteHelper.isOutbox(mFolder)) {
markRead.setVisible(false);
markUnread.setVisible(false);
}
// Only allow moving from the trash
// TODO change this when user folders are implemented
if (!BoteHelper.isTrash(mFolder))
moveTo.setVisible(false);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
// Here you can perform updates to the CAB due to
// an invalidate() request
if (!BoteHelper.isOutbox(mFolder)) {
menu.findItem(R.id.action_mark_read).setVisible(areUnread);
menu.findItem(R.id.action_mark_unread).setVisible(!areUnread);
}
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
}
}
// Called by EmailListActivity.onIdentitySelected()
public void onIdentitySelected() {
getLoaderManager().restartLoader(EMAIL_LIST_LOADER, null, this);
}
// Called by EmailListActivity.onFolderSelected()
public void onFolderSelected(EmailFolder newFolder) {
List<Integer> toMove = mAdapter.getSelectedItems();
for (int i = (toMove.size() - 1); i >= 0; i--) {
Email email = mAdapter.getEmail(toMove.get(i));
mFolder.move(email, newFolder);
}
mMultiSelectController.finish();
}
// LoaderManager.LoaderCallbacks<List<Email>>
public Loader<List<Email>> onCreateLoader(int id, Bundle args) {
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
FolderListener {
private EmailFolder mFolder;
private String mSelectedIdentityKey;
public EmailListLoader(Context context, EmailFolder folder, String selectedIdentityKey) {
super(context);
mFolder = folder;
mSelectedIdentityKey = selectedIdentityKey;
}
@Override
public List<Email> loadInBackground() {
List<Email> emails = null;
try {
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) {
// XXX: Should not get here.
} catch (MessagingException e) {
e.printStackTrace();
} catch (GeneralSecurityException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return emails;
}
protected void onStartMonitoring() {
mFolder.addFolderListener(this);
}
protected void onStopMonitoring() {
mFolder.removeFolderListener(this);
}
protected void releaseResources(List<Email> data) {
}
// FolderListener
@Override
public void elementAdded(String messageId) {
onContentChanged();
}
@Override
public void elementUpdated() {
onContentChanged();
}
@Override
public void elementRemoved(String messageId) {
onContentChanged();
}
}
public void onLoadFinished(Loader<List<Email>> loader,
List<Email> data) {
// Clear recent flags
for (Email email : data)
try {
email.setFlag(Flag.RECENT, false);
} catch (MessagingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mAdapter.setEmails(data);
try {
getActivity().setTitle(
BoteHelper.getFolderDisplayNameWithNew(getActivity(), mFolder));
} catch (PasswordException e) {
// Should not get here.
Log log = I2PAppContext.getGlobalContext().logManager().getLog(EmailListFragment.class);
if (log.shouldLog(Log.WARN))
log.warn("Email list loader finished, but password is no longer cached", e);
} catch (MessagingException | GeneralSecurityException | IOException e) {
e.printStackTrace();
}
}
public void onLoaderReset(Loader<List<Email>> loader) {
mAdapter.setEmails(null);
getActivity().setTitle(
BoteHelper.getFolderDisplayName(getActivity(), mFolder));
}
// SwipeRefreshLayout.OnRefreshListener
public void onRefresh() {
// If we are already checking, do nothing else
if (mCheckingTask != null)
return;
I2PBote bote = I2PBote.getInstance();
if (bote.isConnected()) {
try {
if (!bote.isCheckingForMail())
bote.checkForMail();
mCheckingTask = new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
while (I2PBote.getInstance().isCheckingForMail()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
if (isCancelled()) {
break;
}
}
return null;
}
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
mAdapter.setIncompleteEmails(I2PBote.getInstance().getNumIncompleteEmails());
// Notify PullToRefreshLayout that the refresh has finished
mSwipeRefreshLayout.setRefreshing(false);
getActivity().supportInvalidateOptionsMenu();
}
};
mCheckingTask.execute();
} catch (PasswordException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (GeneralSecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else {
mSwipeRefreshLayout.setRefreshing(false);
Toast.makeText(getActivity(), R.string.bote_needs_to_be_connected, Toast.LENGTH_SHORT).show();
}
}
}