mirror of
https://github.com/M66B/FairEmail.git
synced 2025-02-24 07:01:05 +00:00
Added save all attachments
This commit is contained in:
parent
8ad66eb162
commit
b42b284e41
6 changed files with 207 additions and 70 deletions
1
FAQ.md
1
FAQ.md
|
@ -46,7 +46,6 @@ None at this moment.
|
|||
* Select identities to show in unified inbox: this would add complexity for something which would hardly be used.
|
||||
* Better design: please let me know what you have in mind [in this forum](https://forum.xda-developers.com/android/apps-games/source-email-t3824168).
|
||||
* Hide archived messages: hiding archived messages which exists in other folders too would have a performance impact.
|
||||
* Save all attachments: there is no [Storage Access Framework](https://developer.android.com/guide/topics/providers/document-provider) API to selected multiple files to save.
|
||||
* S/MIME encryption: only PGP encryption will be supported, see [this FAQ](#user-content-faq12) for more information.
|
||||
* ActiveSync: there are no maintained, open source libraries providing the ActiveSync protocol, so this cannot be added.
|
||||
|
||||
|
|
|
@ -93,6 +93,7 @@ import androidx.annotation.Nullable;
|
|||
import androidx.appcompat.app.ActionBarDrawerToggle;
|
||||
import androidx.constraintlayout.widget.Group;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
@ -123,7 +124,8 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
|
|||
static final int REQUEST_THREAD = 2;
|
||||
|
||||
static final int REQUEST_ATTACHMENT = 1;
|
||||
static final int REQUEST_DECRYPT = 2;
|
||||
static final int REQUEST_ATTACHMENTS = 2;
|
||||
static final int REQUEST_DECRYPT = 3;
|
||||
|
||||
static final String ACTION_VIEW_MESSAGES = BuildConfig.APPLICATION_ID + ".VIEW_MESSAGES";
|
||||
static final String ACTION_VIEW_THREAD = BuildConfig.APPLICATION_ID + ".VIEW_THREAD";
|
||||
|
@ -131,6 +133,7 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
|
|||
static final String ACTION_EDIT_FOLDER = BuildConfig.APPLICATION_ID + ".EDIT_FOLDER";
|
||||
static final String ACTION_EDIT_ANSWER = BuildConfig.APPLICATION_ID + ".EDIT_ANSWER";
|
||||
static final String ACTION_STORE_ATTACHMENT = BuildConfig.APPLICATION_ID + ".STORE_ATTACHMENT";
|
||||
static final String ACTION_STORE_ATTACHMENTS = BuildConfig.APPLICATION_ID + ".STORE_ATTACHMENTS";
|
||||
static final String ACTION_DECRYPT = BuildConfig.APPLICATION_ID + ".DECRYPT";
|
||||
static final String ACTION_SHOW_PRO = BuildConfig.APPLICATION_ID + ".SHOW_PRO";
|
||||
|
||||
|
@ -466,6 +469,7 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
|
|||
iff.addAction(ACTION_EDIT_FOLDER);
|
||||
iff.addAction(ACTION_EDIT_ANSWER);
|
||||
iff.addAction(ACTION_STORE_ATTACHMENT);
|
||||
iff.addAction(ACTION_STORE_ATTACHMENTS);
|
||||
iff.addAction(ACTION_DECRYPT);
|
||||
iff.addAction(ACTION_SHOW_PRO);
|
||||
lbm.registerReceiver(receiver, iff);
|
||||
|
@ -1066,6 +1070,8 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
|
|||
onEditAnswer(intent);
|
||||
else if (ACTION_STORE_ATTACHMENT.equals(action))
|
||||
onStoreAttachment(intent);
|
||||
else if (ACTION_STORE_ATTACHMENTS.equals(action))
|
||||
onStoreAttachments(intent);
|
||||
else if (ACTION_DECRYPT.equals(action))
|
||||
onDecrypt(intent);
|
||||
else if (ACTION_SHOW_PRO.equals(action))
|
||||
|
@ -1154,6 +1160,16 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
|
|||
startActivityForResult(Helper.getChooser(this, create), REQUEST_ATTACHMENT);
|
||||
}
|
||||
|
||||
private void onStoreAttachments(Intent intent) {
|
||||
message = intent.getLongExtra("id", -1);
|
||||
Intent tree = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||
//tree.putExtra("android.content.extra.SHOW_ADVANCED", true);
|
||||
if (tree.resolveActivity(getPackageManager()) == null)
|
||||
Snackbar.make(getVisibleView(), R.string.title_no_saf, Snackbar.LENGTH_LONG).show();
|
||||
else
|
||||
startActivityForResult(Helper.getChooser(this, tree), REQUEST_ATTACHMENTS);
|
||||
}
|
||||
|
||||
private void onDecrypt(Intent intent) {
|
||||
if (Helper.isPro(this)) {
|
||||
if (pgpService.isBound()) {
|
||||
|
@ -1337,75 +1353,152 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
|
|||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (resultCode == Activity.RESULT_OK)
|
||||
if (requestCode == REQUEST_ATTACHMENT) {
|
||||
if (data != null) {
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", attachment);
|
||||
args.putParcelable("uri", data.getData());
|
||||
if (data != null)
|
||||
saveAttachment(data);
|
||||
} else if (requestCode == REQUEST_ATTACHMENTS) {
|
||||
if (data != null)
|
||||
saveAttachments(data);
|
||||
|
||||
new SimpleTask<Void>() {
|
||||
@Override
|
||||
protected Void onExecute(Context context, Bundle args) throws Throwable {
|
||||
long id = args.getLong("id");
|
||||
Uri uri = args.getParcelable("uri");
|
||||
|
||||
if ("file".equals(uri.getScheme()))
|
||||
throw new IllegalArgumentException(context.getString(R.string.title_no_stream));
|
||||
|
||||
File file = EntityAttachment.getFile(context, id);
|
||||
|
||||
ParcelFileDescriptor pfd = null;
|
||||
FileOutputStream fos = null;
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
pfd = context.getContentResolver().openFileDescriptor(uri, "w");
|
||||
fos = new FileOutputStream(pfd.getFileDescriptor());
|
||||
fis = new FileInputStream(file);
|
||||
|
||||
byte[] buffer = new byte[ATTACHMENT_BUFFER_SIZE];
|
||||
int read;
|
||||
while ((read = fis.read(buffer)) != -1)
|
||||
fos.write(buffer, 0, read);
|
||||
} finally {
|
||||
try {
|
||||
if (pfd != null)
|
||||
pfd.close();
|
||||
} catch (Throwable ex) {
|
||||
Log.w(ex);
|
||||
}
|
||||
try {
|
||||
if (fos != null)
|
||||
fos.close();
|
||||
} catch (Throwable ex) {
|
||||
Log.w(ex);
|
||||
}
|
||||
try {
|
||||
if (fis != null)
|
||||
fis.close();
|
||||
} catch (Throwable ex) {
|
||||
Log.w(ex);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onExecuted(Bundle args, Void data) {
|
||||
Toast.makeText(ActivityView.this, R.string.title_attachment_saved, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onException(Bundle args, Throwable ex) {
|
||||
if (ex instanceof IllegalArgumentException)
|
||||
Snackbar.make(getVisibleView(), ex.getMessage(), Snackbar.LENGTH_LONG).show();
|
||||
else
|
||||
Helper.unexpectedError(ActivityView.this, ActivityView.this, ex);
|
||||
}
|
||||
}.execute(this, args);
|
||||
}
|
||||
} else if (requestCode == REQUEST_DECRYPT) {
|
||||
if (data != null)
|
||||
decrypt(data, message);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveAttachment(Intent data) {
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", attachment);
|
||||
args.putParcelable("uri", data.getData());
|
||||
|
||||
new SimpleTask<Void>() {
|
||||
@Override
|
||||
protected Void onExecute(Context context, Bundle args) throws Throwable {
|
||||
long id = args.getLong("id");
|
||||
Uri uri = args.getParcelable("uri");
|
||||
|
||||
if ("file".equals(uri.getScheme()))
|
||||
throw new IllegalArgumentException(context.getString(R.string.title_no_stream));
|
||||
|
||||
File file = EntityAttachment.getFile(context, id);
|
||||
|
||||
ParcelFileDescriptor pfd = null;
|
||||
FileOutputStream fos = null;
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
pfd = context.getContentResolver().openFileDescriptor(uri, "w");
|
||||
fos = new FileOutputStream(pfd.getFileDescriptor());
|
||||
fis = new FileInputStream(file);
|
||||
|
||||
byte[] buffer = new byte[ATTACHMENT_BUFFER_SIZE];
|
||||
int read;
|
||||
while ((read = fis.read(buffer)) != -1)
|
||||
fos.write(buffer, 0, read);
|
||||
} finally {
|
||||
try {
|
||||
if (pfd != null)
|
||||
pfd.close();
|
||||
} catch (Throwable ex) {
|
||||
Log.w(ex);
|
||||
}
|
||||
try {
|
||||
if (fos != null)
|
||||
fos.close();
|
||||
} catch (Throwable ex) {
|
||||
Log.w(ex);
|
||||
}
|
||||
try {
|
||||
if (fis != null)
|
||||
fis.close();
|
||||
} catch (Throwable ex) {
|
||||
Log.w(ex);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onExecuted(Bundle args, Void data) {
|
||||
Toast.makeText(ActivityView.this, R.string.title_attachment_saved, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onException(Bundle args, Throwable ex) {
|
||||
if (ex instanceof IllegalArgumentException)
|
||||
Snackbar.make(getVisibleView(), ex.getMessage(), Snackbar.LENGTH_LONG).show();
|
||||
else
|
||||
Helper.unexpectedError(ActivityView.this, ActivityView.this, ex);
|
||||
}
|
||||
}.execute(this, args);
|
||||
}
|
||||
|
||||
private void saveAttachments(Intent data) {
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", message);
|
||||
args.putParcelable("uri", data.getData());
|
||||
|
||||
new SimpleTask<Void>() {
|
||||
@Override
|
||||
protected Void onExecute(Context context, Bundle args) throws Throwable {
|
||||
long id = args.getLong("id");
|
||||
Uri uri = args.getParcelable("uri");
|
||||
|
||||
DB db = DB.getInstance(context);
|
||||
DocumentFile tree = DocumentFile.fromTreeUri(context, uri);
|
||||
for (EntityAttachment attachment : db.attachment().getAttachments(id)) {
|
||||
File file = EntityAttachment.getFile(context, attachment.id);
|
||||
|
||||
String name = attachment.name;
|
||||
if (TextUtils.isEmpty(name))
|
||||
name = Long.toString(attachment.id);
|
||||
DocumentFile document = tree.createFile(attachment.type, name);
|
||||
|
||||
ParcelFileDescriptor pfd = null;
|
||||
FileOutputStream fos = null;
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
pfd = context.getContentResolver().openFileDescriptor(document.getUri(), "w");
|
||||
fos = new FileOutputStream(pfd.getFileDescriptor());
|
||||
fis = new FileInputStream(file);
|
||||
|
||||
byte[] buffer = new byte[ATTACHMENT_BUFFER_SIZE];
|
||||
int read;
|
||||
while ((read = fis.read(buffer)) != -1)
|
||||
fos.write(buffer, 0, read);
|
||||
} finally {
|
||||
try {
|
||||
if (pfd != null)
|
||||
pfd.close();
|
||||
} catch (Throwable ex) {
|
||||
Log.w(ex);
|
||||
}
|
||||
try {
|
||||
if (fos != null)
|
||||
fos.close();
|
||||
} catch (Throwable ex) {
|
||||
Log.w(ex);
|
||||
}
|
||||
try {
|
||||
if (fis != null)
|
||||
fis.close();
|
||||
} catch (Throwable ex) {
|
||||
Log.w(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onExecuted(Bundle args, Void data) {
|
||||
Toast.makeText(ActivityView.this, R.string.title_attachment_saved, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onException(Bundle args, Throwable ex) {
|
||||
Helper.unexpectedError(ActivityView.this, ActivityView.this, ex);
|
||||
}
|
||||
}.execute(this, args);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -182,6 +182,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
private TextView tvNoInternetHeaders;
|
||||
|
||||
private RecyclerView rvAttachment;
|
||||
private Button btnDownloadAttachments;
|
||||
private TextView tvNoInternetAttachments;
|
||||
|
||||
private BottomNavigationView bnvActions;
|
||||
|
@ -249,6 +250,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
adapter = new AdapterAttachment(context, owner, true);
|
||||
rvAttachment.setAdapter(adapter);
|
||||
|
||||
btnDownloadAttachments = itemView.findViewById(R.id.btnDownloadAttachments);
|
||||
tvNoInternetAttachments = itemView.findViewById(R.id.tvNoInternetAttachments);
|
||||
|
||||
bnvActions = itemView.findViewById(R.id.bnvActions);
|
||||
|
@ -271,6 +273,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
ivFlagged.setOnClickListener(this);
|
||||
ivExpanderAddress.setOnClickListener(this);
|
||||
ivAddContact.setOnClickListener(this);
|
||||
btnDownloadAttachments.setOnClickListener(this);
|
||||
btnHtml.setOnClickListener(this);
|
||||
ibQuotes.setOnClickListener(this);
|
||||
ibImages.setOnClickListener(this);
|
||||
|
@ -283,6 +286,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
ivFlagged.setOnClickListener(null);
|
||||
ivExpanderAddress.setOnClickListener(null);
|
||||
ivAddContact.setOnClickListener(null);
|
||||
btnDownloadAttachments.setOnClickListener(null);
|
||||
btnHtml.setOnClickListener(null);
|
||||
ibQuotes.setOnClickListener(null);
|
||||
ibImages.setOnClickListener(null);
|
||||
|
@ -314,6 +318,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
pbHeaders.setVisibility(View.GONE);
|
||||
tvNoInternetHeaders.setVisibility(View.GONE);
|
||||
|
||||
btnDownloadAttachments.setVisibility(View.GONE);
|
||||
tvNoInternetAttachments.setVisibility(View.GONE);
|
||||
|
||||
bnvActions.setVisibility(View.GONE);
|
||||
|
@ -436,6 +441,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
ivDraft.setVisibility(message.drafts > 0 ? View.VISIBLE : View.GONE);
|
||||
ivAnswered.setVisibility(message.ui_answered ? View.VISIBLE : View.GONE);
|
||||
ivAttachments.setVisibility(message.attachments > 0 ? View.VISIBLE : View.GONE);
|
||||
btnDownloadAttachments.setVisibility(View.GONE);
|
||||
tvNoInternetAttachments.setVisibility(View.GONE);
|
||||
tvSubject.setText(message.subject);
|
||||
|
||||
|
@ -584,12 +590,17 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
adapter.set(attachments);
|
||||
|
||||
boolean downloading = false;
|
||||
for (EntityAttachment attachment : attachments)
|
||||
boolean all = (attachments.size() > 1);
|
||||
for (EntityAttachment attachment : attachments) {
|
||||
if (attachment.progress != null) {
|
||||
downloading = true;
|
||||
break;
|
||||
}
|
||||
if (!attachment.available)
|
||||
all = false;
|
||||
}
|
||||
|
||||
btnDownloadAttachments.setVisibility(all ? View.VISIBLE : View.GONE);
|
||||
tvNoInternetAttachments.setVisibility(downloading && !internet ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (message.content) {
|
||||
|
@ -694,6 +705,8 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
else if (viewType == ViewType.THREAD) {
|
||||
if (view.getId() == R.id.ivExpanderAddress)
|
||||
onToggleAddresses(pos, message);
|
||||
else if (view.getId() == R.id.btnDownloadAttachments)
|
||||
onDownloadAttachments(message);
|
||||
else if (view.getId() == R.id.btnHtml)
|
||||
onShowHtml(message);
|
||||
else if (view.getId() == R.id.ibQuotes)
|
||||
|
@ -793,6 +806,13 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
notifyItemChanged(pos);
|
||||
}
|
||||
|
||||
private void onDownloadAttachments(TupleMessageEx message) {
|
||||
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
|
||||
lbm.sendBroadcast(
|
||||
new Intent(ActivityView.ACTION_STORE_ATTACHMENTS)
|
||||
.putExtra("id", message.id));
|
||||
}
|
||||
|
||||
private void onShowHtml(final TupleMessageEx message) {
|
||||
if (confirm)
|
||||
new DialogBuilderLifecycle(context, owner)
|
||||
|
|
|
@ -554,15 +554,28 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/vSeparatorAttachments" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnDownloadAttachments"
|
||||
style="?android:attr/buttonStyleSmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="0dp"
|
||||
android:minHeight="0dp"
|
||||
android:text="@string/title_save_all"
|
||||
app:layout_constraintEnd_toEndOf="@id/rvAttachment"
|
||||
app:layout_constraintTop_toBottomOf="@id/rvAttachment" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvNoInternetAttachments"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:text="@string/title_no_internet"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintEnd_toEndOf="@id/rvAttachment"
|
||||
app:layout_constraintStart_toStartOf="@id/rvAttachment"
|
||||
app:layout_constraintTop_toBottomOf="@id/rvAttachment" />
|
||||
app:layout_constraintTop_toBottomOf="@id/btnDownloadAttachments" />
|
||||
|
||||
<com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
android:id="@+id/bnvActions"
|
||||
|
|
|
@ -546,6 +546,17 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/vSeparatorAttachments" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnDownloadAttachments"
|
||||
style="?android:attr/buttonStyleSmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="0dp"
|
||||
android:minHeight="0dp"
|
||||
android:text="@string/title_save_all"
|
||||
app:layout_constraintEnd_toEndOf="@id/rvAttachment"
|
||||
app:layout_constraintTop_toBottomOf="@id/rvAttachment" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvNoInternetAttachments"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -554,7 +565,7 @@
|
|||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintEnd_toEndOf="@id/rvAttachment"
|
||||
app:layout_constraintStart_toStartOf="@id/rvAttachment"
|
||||
app:layout_constraintTop_toBottomOf="@id/rvAttachment" />
|
||||
app:layout_constraintTop_toBottomOf="@id/btnDownloadAttachments" />
|
||||
|
||||
<com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
android:id="@+id/bnvActions"
|
||||
|
|
|
@ -264,6 +264,7 @@
|
|||
<string name="title_show_headers">Show headers</string>
|
||||
<string name="title_manage_keywords">Manage keywords</string>
|
||||
<string name="title_add_keyword">Add keyword</string>
|
||||
<string name="title_save_all">Save all</string>
|
||||
<string name="title_show_html">Show original</string>
|
||||
|
||||
<string name="title_trash">Trash</string>
|
||||
|
|
Loading…
Reference in a new issue