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 0000000000..93dccddabc Binary files /dev/null and b/res/drawable-hdpi/ic_save_all_white_24dp.png differ diff --git a/res/drawable-mdpi/ic_save_all_white_24dp.png b/res/drawable-mdpi/ic_save_all_white_24dp.png new file mode 100644 index 0000000000..67281793a7 Binary files /dev/null and b/res/drawable-mdpi/ic_save_all_white_24dp.png differ 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 0000000000..799802dd6b Binary files /dev/null and b/res/drawable-xhdpi/ic_save_all_white_24dp.png differ diff --git a/res/drawable-xxhdpi/ic_save_all_white_24dp.png b/res/drawable-xxhdpi/ic_save_all_white_24dp.png new file mode 100644 index 0000000000..1c6ba846a8 Binary files /dev/null and b/res/drawable-xxhdpi/ic_save_all_white_24dp.png differ diff --git a/res/drawable-xxxhdpi/ic_save_all_white_24dp.png b/res/drawable-xxxhdpi/ic_save_all_white_24dp.png new file mode 100644 index 0000000000..94f67c9d37 Binary files /dev/null and b/res/drawable-xxxhdpi/ic_save_all_white_24dp.png differ 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