From 97f290f2729591025bc803ec70a8b06b8749412a Mon Sep 17 00:00:00 2001 From: str4d Date: Sun, 28 Dec 2014 20:16:06 +0000 Subject: [PATCH] Add/remove attachments in new email, list attachments in view email --- .../i2p/bote/android/NewEmailFragment.java | 91 +++++++++++- .../i2p/bote/android/ViewEmailFragment.java | 18 +++ .../bote/android/util/ContentAttachment.java | 129 ++++++++++++++++++ .../ic_attach_file_white_24dp.png | Bin 0 -> 452 bytes .../ic_attach_file_white_24dp.png | Bin 0 -> 332 bytes .../ic_attach_file_white_24dp.png | Bin 0 -> 576 bytes .../ic_attach_file_white_24dp.png | Bin 0 -> 870 bytes .../main/res/layout/fragment_new_email.xml | 6 + .../main/res/layout/fragment_view_email.xml | 6 + app/src/main/res/menu/new_email.xml | 6 + app/src/main/res/values/strings.xml | 5 + 11 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/i2p/bote/android/util/ContentAttachment.java create mode 100644 app/src/main/res/drawable-hdpi/ic_attach_file_white_24dp.png create mode 100644 app/src/main/res/drawable-mdpi/ic_attach_file_white_24dp.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_attach_file_white_24dp.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_attach_file_white_24dp.png diff --git a/app/src/main/java/i2p/bote/android/NewEmailFragment.java b/app/src/main/java/i2p/bote/android/NewEmailFragment.java index 0baf19f..2d72098 100644 --- a/app/src/main/java/i2p/bote/android/NewEmailFragment.java +++ b/app/src/main/java/i2p/bote/android/NewEmailFragment.java @@ -1,14 +1,20 @@ package i2p.bote.android; +import android.annotation.SuppressLint; import android.app.Activity; import android.app.AlertDialog; +import android.content.ClipData; import android.content.Context; import android.content.DialogInterface; +import android.content.Intent; import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.support.v4.app.Fragment; import android.text.Editable; import android.text.TextWatcher; +import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -18,6 +24,7 @@ import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.Spinner; import android.widget.TextView; @@ -25,6 +32,7 @@ import com.tokenautocomplete.FilteredArrayAdapter; import net.i2p.data.DataFormatException; +import java.io.FileNotFoundException; import java.io.IOException; import java.security.GeneralSecurityException; import java.util.ArrayList; @@ -40,6 +48,7 @@ import javax.mail.internet.InternetAddress; import i2p.bote.I2PBote; import i2p.bote.android.util.BoteHelper; import i2p.bote.android.util.ContactsCompletionView; +import i2p.bote.android.util.ContentAttachment; import i2p.bote.android.util.Person; import i2p.bote.email.Attachment; import i2p.bote.email.Email; @@ -89,6 +98,8 @@ public class NewEmailFragment extends Fragment { public static final String QUOTE_MSG_TYPE = "type"; + private static final int REQUEST_FILE = 1; + private String mSenderKey; Spinner mSpinner; @@ -100,6 +111,7 @@ public class NewEmailFragment extends Fragment { ContactsCompletionView mBcc; EditText mSubject; EditText mContent; + LinearLayout mAttachments; boolean mMoreVisible; boolean mDirty; @@ -137,6 +149,7 @@ public class NewEmailFragment extends Fragment { mBcc = (ContactsCompletionView) view.findViewById(R.id.bcc); mSubject = (EditText) view.findViewById(R.id.subject); mContent = (EditText) view.findViewById(R.id.message); + mAttachments = (LinearLayout) view.findViewById(R.id.attachments); String quoteMsgFolder = getArguments().getString(QUOTE_MSG_FOLDER); String quoteMsgId = getArguments().getString(QUOTE_MSG_ID); @@ -357,6 +370,10 @@ public class NewEmailFragment extends Fragment { @Override public boolean onOptionsItemSelected(final MenuItem item) { switch (item.getItemId()) { + case R.id.action_attach_file: + requestFile(); + return true; + case R.id.action_send_email: if (sendEmail()) mCallbacks.onTaskFinished(); @@ -388,6 +405,64 @@ public class NewEmailFragment extends Fragment { } } + private void requestFile() { + Intent i = new Intent(Intent.ACTION_GET_CONTENT); + i.setType("*/*"); + i.addCategory(Intent.CATEGORY_OPENABLE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) + i.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); + startActivityForResult( + Intent.createChooser(i, + getResources().getString(R.string.select_attachment)), + REQUEST_FILE); + } + + @SuppressLint("NewApi") + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode != Activity.RESULT_OK) { + if (resultCode == Activity.RESULT_CANCELED) { + System.out.println("Cancelled"); + } + return; + } + + switch (requestCode) { + case REQUEST_FILE: + addAttachment(data.getData()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && + data.getClipData() != null) { + ClipData clipData = data.getClipData(); + for (int i = 0; i < clipData.getItemCount(); i++) { + addAttachment(clipData.getItemAt(i).getUri()); + } + } + break; + } + } + + private void addAttachment(Uri uri) { + // Try to create a ContentAttachment using the provided Uri. + try { + final ContentAttachment attachment = new ContentAttachment(getActivity().getContentResolver(), uri); + final View v = getActivity().getLayoutInflater().inflate(R.layout.listitem_attachment, mAttachments, false); + v.setTag(attachment); + ((TextView) v.findViewById(R.id.filename)).setText(attachment.getFileName()); + ((TextView) v.findViewById(R.id.size)).setText(attachment.getHumanReadableSize(getActivity())); + v.findViewById(R.id.remove_attachment).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + attachment.clean(); + mAttachments.removeView(v); + } + }); + mAttachments.addView(v); + } catch (FileNotFoundException e) { + e.printStackTrace(); + Log.e(Constants.ANDROID_LOG_TAG, "File not found: " + uri); + } + } + private boolean sendEmail() { Email email = new Email(I2PBote.getInstance().getConfiguration().getIncludeSentTime()); try { @@ -433,11 +508,25 @@ public class NewEmailFragment extends Fragment { email.setSubject(mSubject.getText().toString(), "UTF-8"); + // Extract the attachments + List attachments = new ArrayList(); + for (int i = 0; i < mAttachments.getChildCount(); i++) { + View v = mAttachments.getChildAt(i); + attachments.add((Attachment) v.getTag()); + } + // Set the text and add attachments - email.setContent(mContent.getText().toString(), (List) null); + email.setContent(mContent.getText().toString(), attachments); // Send the email I2PBote.getInstance().sendEmail(email); + + // Clean up attachments + for (Attachment attachment : attachments) { + if (!attachment.clean()) + Log.e(Constants.ANDROID_LOG_TAG, "Can't clean up attachment: <" + attachment + ">"); + } + return true; } catch (PasswordException e) { // TODO Auto-generated catch block diff --git a/app/src/main/java/i2p/bote/android/ViewEmailFragment.java b/app/src/main/java/i2p/bote/android/ViewEmailFragment.java index 8c1b2ad..0a5c5c0 100644 --- a/app/src/main/java/i2p/bote/android/ViewEmailFragment.java +++ b/app/src/main/java/i2p/bote/android/ViewEmailFragment.java @@ -20,11 +20,15 @@ import android.widget.Toast; import java.io.IOException; import java.security.GeneralSecurityException; import java.text.DateFormat; +import java.util.List; import javax.mail.Address; import javax.mail.MessagingException; +import javax.mail.Part; +import i2p.bote.Util; import i2p.bote.android.util.BoteHelper; +import i2p.bote.android.util.ContentAttachment; import i2p.bote.email.Email; import i2p.bote.fileencryption.PasswordException; @@ -84,6 +88,7 @@ public class ViewEmailFragment extends Fragment { TextView sent = (TextView) v.findViewById(R.id.email_sent); TextView received = (TextView) v.findViewById(R.id.email_received); TextView content = (TextView) v.findViewById(R.id.email_content); + LinearLayout attachments = (LinearLayout) v.findViewById(R.id.attachments); try { String fromAddress = email.getOneFromAddress(); @@ -160,6 +165,19 @@ public class ViewEmailFragment extends Fragment { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) content.setTextIsSelectable(true); + List parts = email.getParts(); + for (int partIndex=0; partIndex < parts.size(); partIndex++) { + Part part = parts.get(partIndex); + if (Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition())) { + ContentAttachment attachment = new ContentAttachment(part); + final View a = getActivity().getLayoutInflater().inflate(R.layout.listitem_attachment, attachments, false); + ((TextView)a.findViewById(R.id.filename)).setText(attachment.getFileName()); + ((TextView)a.findViewById(R.id.size)).setText(attachment.getHumanReadableSize(getActivity())); + a.findViewById(R.id.remove_attachment).setVisibility(View.GONE); + attachments.addView(a); + } + } + // Prepare fields for replying mIsAnonymous = email.isAnonymous(); } catch (MessagingException e) { diff --git a/app/src/main/java/i2p/bote/android/util/ContentAttachment.java b/app/src/main/java/i2p/bote/android/util/ContentAttachment.java new file mode 100644 index 0000000..9861d6a --- /dev/null +++ b/app/src/main/java/i2p/bote/android/util/ContentAttachment.java @@ -0,0 +1,129 @@ +package i2p.bote.android.util; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.ParcelFileDescriptor; +import android.provider.OpenableColumns; +import android.util.Log; + +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.text.NumberFormat; +import java.util.Locale; + +import javax.activation.DataHandler; +import javax.activation.DataSource; +import javax.mail.MessagingException; +import javax.mail.Part; + +import i2p.bote.Util; +import i2p.bote.android.Constants; +import i2p.bote.android.R; +import i2p.bote.email.Attachment; + +public class ContentAttachment implements Attachment { + private ParcelFileDescriptor mAttachmentPFD; + private String mFileName; + private long mSize; + private DataHandler mDataHandler; + + public ContentAttachment(ContentResolver cr, Uri uri) throws FileNotFoundException { + // Get the content resolver instance for this context, and use it + // to get a ParcelFileDescriptor for the file. + mAttachmentPFD = cr.openFileDescriptor(uri, "r"); + // If we get to here, the file exists + + Cursor returnCursor = cr.query( + uri, + new String[]{OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE}, + null, null, null); + int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); + int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE); + returnCursor.moveToFirst(); + mFileName = returnCursor.getString(nameIndex); + mSize = returnCursor.getLong(sizeIndex); + returnCursor.close(); + + // Get a regular file descriptor for the file + final FileDescriptor fd = mAttachmentPFD.getFileDescriptor(); + final String mimeType = cr.getType(uri); + mDataHandler = new DataHandler(new DataSource() { + @Override + public InputStream getInputStream() throws IOException { + return new FileInputStream(fd); + } + + @Override + public OutputStream getOutputStream() throws IOException { + throw new IOException("Cannot write to attachments"); + } + + @Override + public String getContentType() { + return mimeType; + } + + @Override + public String getName() { + return mFileName; + } + }); + } + + public ContentAttachment(final Part part) throws IOException, MessagingException { + mFileName = part.getFileName(); + mSize = Util.getPartSize(part); + mDataHandler = part.getDataHandler(); + } + + @Override + public String getFileName() { + return mFileName; + } + + public long getSize() { + return mSize; + } + + public String getHumanReadableSize(Context context) { + int unit = (63-Long.numberOfLeadingZeros(mSize)) / 10; // 0 if totalBytes<1K, 1 if 1K<=totalBytes<1M, etc. + double value = (double)mSize / (1<<(10*unit)); + int formatStr; + switch (unit) { + case 0: formatStr = R.string.n_bytes; break; + case 1: formatStr = R.string.n_kilobytes; break; + default: formatStr = R.string.n_megabytes; + } + NumberFormat formatter = NumberFormat.getInstance(Locale.getDefault()); + if (value < 100) + formatter.setMaximumFractionDigits(1); + else + formatter.setMaximumFractionDigits(0); + return context.getString(formatStr, formatter.format(value)); + } + + @Override + public DataHandler getDataHandler() { + return mDataHandler; + } + + @Override + public boolean clean() { + if (mAttachmentPFD == null) + return true; + + try { + mAttachmentPFD.close(); + return true; + } catch (IOException e) { + Log.e(Constants.ANDROID_LOG_TAG, "Can't close ParcelFileDescriptor: <" + mFileName + ">", e); + return false; + } + } +} diff --git a/app/src/main/res/drawable-hdpi/ic_attach_file_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_attach_file_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..66299b88178eb314e3de58e91af24cb273dd8497 GIT binary patch literal 452 zcmV;#0XzPQP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00BHnL_t(Y$L*G}O2beThW`p;6131oJ9U&oC3Mi|kk0xN z)dvy53Z)OByQnx9T}sg`BIvF|s!5m6CDx|RjY$y~^PlPd=kgIw;GSQ|P!?_a_=MbX zh;6(Vm8nrCRP z!MOquqR!i7MZU5b>t)&^-UEi%@!Yy}A&Mr z!@iQVm)O~lenG}zH-UhyIo~A&+&rO=~G=WkKx z#M8ww#NzbZD~4XofdXs~%6SX9O?#gxH7)lN2#$0)ow3}qVUlo5MCbO1pcKI=+i!QA zOc#tOxw<_&U6HxJ(!S>MS(b<8Urk@lUODBROc`UXe%P^dqVx7EpG`Ax004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00Fp3L_t(o!|j;AYZOrsfWH;VZS10G?8HWr>PR0IwXzVg z@;@+QtOWZYf?zK>g8zUZVi(J3o91#BVmeX5&f+}Hrtq`aO~~f%_Gb(U?7U`oW_G@} zyYt@8Y#{$vPH-dub9C4wBH}a4v`Z1p8j129 zj%il*Xlhm0vBbxBh6>r@}uQ<5dcXU~4g8vtC-^trLevvd}{1K@r}4H|F{fYoYQbjDw*E{ye9RJHF^)fVch zs(-vX(t6D;RhQ~CfgcmZfxR`|R+Nut~i+)csz3T&KY}s004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00Q1gL_t(&-tC&tYZE~f$G(5&$SW!t(3Km4A2Ba57 z&_gL!5DnHMo|Gb9JQQ#F7ZjloJgHdxfr1Dc)KuxA;%_VnX=_@AV$BL3Y>TZ8Uk{l~ zH{0EGce9&Dm^X(vJNw@5Co}KOY<4F=)RwG*0xB;d*PcWk2g{sV=Do-b=PCv=^9{HR5sVrnGHS|t`|JvC$sNC7Dz1*Cu!kOERb3P=GdAO)m= z6i|)*w=YP*ln%i z9dj5KF@wKnL-tDO&2sXyBdur>wIQ#8QnGf|f!8?`e~)4a92L>J| zctK8aFLV>1bqR7%di3}Kw1(g04KCZTzbdWAS?|wh@}s)t;Y!(F3hHpk4*$N}Cv~@Z z$?cZ0GpMuc^K&CXJqL8Rnq!iC3>K}tCPSGdpRvxrjRb|I;34*xv`pePCNP6_h#)5C zoq{|^r_DvfMcOa+Qj+$h&q?pN-HcKwNBDIam~s1|>;(|Gn&&xU0h3{m-9)cEhKx(+ wA?!ml8nG6$$RUFvq~z1KV+p8DasM08KOa<2@9AQGqyPW_07*qoM6N<$f|J*Q761SM literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/fragment_new_email.xml b/app/src/main/res/layout/fragment_new_email.xml index 506ad11..6357506 100644 --- a/app/src/main/res/layout/fragment_new_email.xml +++ b/app/src/main/res/layout/fragment_new_email.xml @@ -68,6 +68,12 @@ android:ems="10" android:hint="@string/compose_email" android:inputType="textMultiLine"/> + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_view_email.xml b/app/src/main/res/layout/fragment_view_email.xml index 6205564..423f8ca 100644 --- a/app/src/main/res/layout/fragment_view_email.xml +++ b/app/src/main/res/layout/fragment_view_email.xml @@ -221,6 +221,12 @@ android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:textAppearance="@style/TextAppearance.AppCompat.Primary"/> + + diff --git a/app/src/main/res/menu/new_email.xml b/app/src/main/res/menu/new_email.xml index 5d06f72..af7612f 100644 --- a/app/src/main/res/menu/new_email.xml +++ b/app/src/main/res/menu/new_email.xml @@ -2,6 +2,12 @@ + + Log out Touch the lock icon to log in. New email + Attach file + Select a file to attach + %s B + %s KB + %s MB Send email Connect to network Disconnect from network