From 238471b847e6d106af9eaabcf59b38104f9e0097 Mon Sep 17 00:00:00 2001 From: Andreas Fehn Date: Sat, 22 Aug 2015 13:03:07 +0200 Subject: [PATCH] Allow saving all attachments of a thread Closes #3975 --- res/drawable-hdpi/ic_save_all_white_24dp.png | Bin 0 -> 404 bytes res/drawable-mdpi/ic_save_all_white_24dp.png | Bin 0 -> 314 bytes res/drawable-xhdpi/ic_save_all_white_24dp.png | Bin 0 -> 472 bytes .../ic_save_all_white_24dp.png | Bin 0 -> 647 bytes .../ic_save_all_white_24dp.png | Bin 0 -> 789 bytes res/menu/media_overview.xml | 8 +++ res/values/strings.xml | 24 +++++-- .../securesms/ConversationFragment.java | 4 +- .../securesms/MediaOverviewActivity.java | 62 ++++++++++++++++- .../securesms/util/SaveAttachmentTask.java | 63 ++++++++++++------ 10 files changed, 136 insertions(+), 25 deletions(-) create mode 100644 res/drawable-hdpi/ic_save_all_white_24dp.png create mode 100644 res/drawable-mdpi/ic_save_all_white_24dp.png create mode 100644 res/drawable-xhdpi/ic_save_all_white_24dp.png create mode 100644 res/drawable-xxhdpi/ic_save_all_white_24dp.png create mode 100644 res/drawable-xxxhdpi/ic_save_all_white_24dp.png create mode 100644 res/menu/media_overview.xml diff --git a/res/drawable-hdpi/ic_save_all_white_24dp.png b/res/drawable-hdpi/ic_save_all_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..93dccddabcce394eb301ce73bdae10277e586a28 GIT binary patch literal 404 zcmV;F0c-w=P)Xlb3M#3GmRiKDnN~&v?yHm-luwXkJpi{=hz(`6);+h~irWJT ziVJiH+)*YLG)>au`VXob;GS;L(0SZ8?OQXWnqVD(t4ay<1RRZq&f%~iPGtaaO$mYS zUSd-;bP@@1p=SQwO;rTeVslEN=E7Slurxk&EG|@4c#8#A#fMI)t&X};)zVLR^Ad-W z-hU{1cic}qbXG@sOJ%fL1QrTBZ@CyF`s&s|`0000;3+_^Jt4N|?3HOM*_Jlh$ToQeTiaYcH&Lv$rcYAlUY0v%mzx>Kd(8q=s zJka?*8{IirW`+3{Bu%kODSjE*9&GX6Be&cWbHBCAu9;-CBK$NW1s65xe2r!bk$g?T zYdjK6HBHQTBm5SL8AfQPJ-8#Z>kFVkaI7P^-T{07u+~me@Cu*pcg}e+ryY?CV2$0R z;E7H*grHP???I{cg1Nr*7Hkz_)0OuIlo&6Fd6$4)odZ}S#KtNFu-b9eDR>Tmzov?J z1Ay%o+mwQ9(lyFfF5SWQ|6zv9s8*^}i3T()Q=&A&H^%&jKi&IU0MtYs`WQzp@&Et; M07*qoM6N<$g2pn2_W%F@ literal 0 HcmV?d00001 diff --git a/res/drawable-xhdpi/ic_save_all_white_24dp.png b/res/drawable-xhdpi/ic_save_all_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..799802dd6b5b4d594eccdd2279c7354e8d5cc9c8 GIT binary patch literal 472 zcmV;}0Vn>6P)Nkl07vm#+qSvXwr$MSSa%;f)wa#4wr$%sW1sRXo^EbFC;6W(`h_ovAeoB;F$_!u z{5U1Ku=9lg4qB=KYf)X0)+)eY#xaCp(t5Fogav4wJg>pDe~4Ccpfr8yMpxwZ_+CK- zf~h3TPb+JnK6X@gn9RCI(ZU>P%rgK*B9n%2(cc94^MZNt;I+s@0RHR<88w8>4A>8V zhZba}glR<)tO5gKstni-fP+d1NwfKX$9}&SlL7c-4S1+21s0;+akBh8=X!Dhu=(dX zmy!>-`172ra==AZDKHoPj)e=jsV)WPqu=pxfe`n(#x0Ak@G(+AVbKW0fsfoJkX*nS zdQyxGRAW4zqzBd_{;3LYlNLCK(9}I?fguQ8$C3`PQPKdIV+nj#0bY?!`i-UqY$^bB zFaS1M0*}>z8cXm?uhozsT^ O0000u-Hpm89 zAC!!-Unpy<}HLV2-pwd4Dnx_KPOi$1Wn$wIn!fQ=E zmg@vk-~SD=K{m(+*&tWWaRqm4fDY1)%A_DIwduu8b3vZ;B1WiFrC>0=W`Vq@j}*aL zngtq!6v^hA0Xl^g>GJT=c+g}dC!1|F$c0i)LEVf7y(FzuP-mk-Z^)$KhkTdlg92#g z6x2r%=tJ~Du+|AD76%l8=4%0ZOTI`!jX((07=WuJ(E09BB*0l^yL zBZXNq_JSa&39o60BnG;RNGBwdc*!}29!*#O7iU81u?rWz@JWMxfohMdQA%+d8caCIHgVC7d6zQS@UD)sUIgBI6$QHX` zK8_qFSL}j$IC6k|u?yzn$bRy~E|`NO`^XWaXvAknp2;htDxj5f4^-kQ^oaDc<2XDwWWHR5A=rDzEV`&Jm9dC+9c)^+h#bk;{c-*Q1X;wX2^h^mk;t`$%D+c^XJ@E*y z7z+4KRlt9&6Ay3;oec$SR22Z95F>7p&1C}tU-Ou{fM4k^Zg3N=z(7C?5p@Aj%7fw( zcT;2_U@t*x1K?xs6{pBzEBF*}2-rlli0S~i$ZJlQ&n@6%Ks!beE@DOid`&Ozlxiqo zI3Iwo0S52~zBtQF`ca3sc$c0`;TS+*0!r}Rw-oaY)tDbJlE9fI#*s&${77>#j@wv8 zVC*Jb{DB-k#(Wn@7Jng!m6+=YvEomJkjGGp_=8^+&lij(jWF?orfHg{X`1#wjGW-I TIF(C<00000NkvXXu0mjf(UWAT literal 0 HcmV?d00001 diff --git a/res/menu/media_overview.xml b/res/menu/media_overview.xml new file mode 100644 index 0000000000..e860d756e0 --- /dev/null +++ b/res/menu/media_overview.xml @@ -0,0 +1,8 @@ + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index ca9adc11f1..ea3818d169 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -161,12 +161,25 @@ This will permanently delete all %1$d selected messages. Save to storage? - Saving this media to storage will allow any other apps on your device to access it.\n\nContinue? - Error while saving attachment to storage! + + Saving this media to storage will allow any other apps on your device to access it.\n\nContinue? + Saving all %1$d media to storage will allow any other apps on your device to access them.\n\nContinue? + + + Error while saving attachment to storage! + Error while saving attachments to storage! + Success! Unable to write to storage! - Saving attachment - Saving attachment to storage... + + Saving attachment + Saving %1$d attachments + + + Saving attachment to storage... + Saving %1$d attachments to storage... + + Collecting attachments... Pending... Data (Signal) MMS @@ -1165,6 +1178,9 @@ Save + + Save all + Image preview diff --git a/src/org/thoughtcrime/securesms/ConversationFragment.java b/src/org/thoughtcrime/securesms/ConversationFragment.java index ec78721b20..7811cfac23 100644 --- a/src/org/thoughtcrime/securesms/ConversationFragment.java +++ b/src/org/thoughtcrime/securesms/ConversationFragment.java @@ -348,7 +348,9 @@ public class ConversationFragment extends Fragment } Log.w(TAG, "No slide with attachable media found, failing nicely."); - Toast.makeText(getActivity(), R.string.ConversationFragment_error_while_saving_attachment_to_sd_card, Toast.LENGTH_LONG).show(); + Toast.makeText(getActivity(), + getResources().getQuantityString(R.plurals.ConversationFragment_error_while_saving_attachments_to_sd_card, 1), + Toast.LENGTH_LONG).show(); } }); } diff --git a/src/org/thoughtcrime/securesms/MediaOverviewActivity.java b/src/org/thoughtcrime/securesms/MediaOverviewActivity.java index bfbd2c2c77..35c0f874fc 100644 --- a/src/org/thoughtcrime/securesms/MediaOverviewActivity.java +++ b/src/org/thoughtcrime/securesms/MediaOverviewActivity.java @@ -18,6 +18,7 @@ package org.thoughtcrime.securesms; import android.annotation.TargetApi; import android.content.Context; +import android.content.DialogInterface; import android.content.res.Configuration; import android.database.Cursor; import android.os.Build.VERSION; @@ -29,6 +30,8 @@ import android.support.v4.content.Loader; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.WindowManager; @@ -37,11 +40,17 @@ import android.widget.TextView; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter; import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.ImageDatabase.ImageRecord; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient.RecipientModifiedListener; import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.util.AbstractCursorLoader; import org.thoughtcrime.securesms.util.DynamicLanguage; +import org.thoughtcrime.securesms.util.SaveAttachmentTask; +import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; + +import java.util.ArrayList; +import java.util.List; /** * Activity for displaying media attachments in-app @@ -137,12 +146,62 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity i } } + private void saveToDisk() { + final Context c = this; + + SaveAttachmentTask.showWarningDialog(this, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + new ProgressDialogAsyncTask>(c, + R.string.ConversationFragment_collecting_attahments, + R.string.please_wait) { + @Override + protected List doInBackground(Void... params) { + Cursor cursor = DatabaseFactory.getImageDatabase(c).getImagesForThread(threadId); + List attachments = new ArrayList<>(cursor.getCount()); + + while (cursor != null && cursor.moveToNext()) { + ImageRecord record = ImageRecord.from(cursor); + attachments.add(new SaveAttachmentTask.Attachment(record.getAttachment().getDataUri(), + record.getContentType(), + record.getDate())); + } + + return attachments; + } + + @Override + protected void onPostExecute(List attachments) { + super.onPostExecute(attachments); + + SaveAttachmentTask saveTask = new SaveAttachmentTask(c, masterSecret, attachments.size()); + saveTask.execute(attachments.toArray(new SaveAttachmentTask.Attachment[attachments.size()])); + } + }.execute(); + } + }, gridView.getAdapter().getItemCount()); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + + menu.clear(); + if (gridView.getAdapter() != null && gridView.getAdapter().getItemCount() > 0) { + MenuInflater inflater = this.getMenuInflater(); + inflater.inflate(R.menu.media_overview, menu); + } + + return true; + } + @Override public boolean onOptionsItemSelected(MenuItem item) { super.onOptionsItemSelected(item); switch (item.getItemId()) { - case android.R.id.home: finish(); return true; + case R.id.save: saveToDisk(); return true; + case android.R.id.home: finish(); return true; } return false; @@ -158,6 +217,7 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity i Log.w(TAG, "onLoadFinished()"); gridView.setAdapter(new ImageMediaAdapter(this, masterSecret, cursor)); noImages.setVisibility(gridView.getAdapter().getItemCount() > 0 ? View.GONE : View.VISIBLE); + invalidateOptionsMenu(); } @Override diff --git a/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java b/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java index 8c4f287cec..376f0bb88d 100644 --- a/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java +++ b/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java @@ -33,18 +33,26 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask contextReference; private final WeakReference masterSecretReference; + private final int attachmentCount; + public SaveAttachmentTask(Context context, MasterSecret masterSecret) { - super(context, R.string.ConversationFragment_saving_attachment, R.string.ConversationFragment_saving_attachment_to_sd_card); + this(context, masterSecret, 1); + } + + public SaveAttachmentTask(Context context, MasterSecret masterSecret, int count) { + super(context, + context.getResources().getQuantityString(R.plurals.ConversationFragment_saving_n_attachments, count, count), + context.getResources().getQuantityString(R.plurals.ConversationFragment_saving_n_attachments_to_sd_card, count, count)); this.contextReference = new WeakReference<>(context); this.masterSecretReference = new WeakReference<>(masterSecret); + this.attachmentCount = count; } @Override protected Integer doInBackground(SaveAttachmentTask.Attachment... attachments) { - if (attachments == null || attachments.length != 1 || attachments[0] == null) { - throw new AssertionError("must pass in exactly one attachment"); + if (attachments == null || attachments.length == 0) { + throw new AssertionError("must pass in at least one attachment"); } - Attachment attachment = attachments[0]; try { Context context = contextReference.get(); @@ -58,20 +66,12 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask