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