diff --git a/CHANGELOG.md b/CHANGELOG.md index 64a38654d9..f8d45548e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ For support you can use [the contact form](https://contact.faircode.eu/?product= ### Next version * Added linking contacts to identities to attach vCards +* Added draft message printing * Small improvements and minor bug fixes * Updated AndroidX * Updated [translations](https://crowdin.com/project/open-source-email) diff --git a/app/src/main/assets/CHANGELOG.md b/app/src/main/assets/CHANGELOG.md index 64a38654d9..f8d45548e1 100644 --- a/app/src/main/assets/CHANGELOG.md +++ b/app/src/main/assets/CHANGELOG.md @@ -9,6 +9,7 @@ For support you can use [the contact form](https://contact.faircode.eu/?product= ### Next version * Added linking contacts to identities to attach vCards +* Added draft message printing * Small improvements and minor bug fixes * Updated AndroidX * Updated [translations](https://crowdin.com/project/open-source-email) diff --git a/app/src/main/java/eu/faircode/email/FragmentCompose.java b/app/src/main/java/eu/faircode/email/FragmentCompose.java index ca6c7309ff..d641376d26 100644 --- a/app/src/main/java/eu/faircode/email/FragmentCompose.java +++ b/app/src/main/java/eu/faircode/email/FragmentCompose.java @@ -325,10 +325,11 @@ public class FragmentCompose extends FragmentBase { private static final int REQUEST_OPENPGP = 10; private static final int REQUEST_CONTACT_GROUP = 11; private static final int REQUEST_SELECT_IDENTITY = 12; - private static final int REQUEST_LINK = 13; - private static final int REQUEST_DISCARD = 14; - private static final int REQUEST_SEND = 15; - private static final int REQUEST_REMOVE_ATTACHMENTS = 16; + private static final int REQUEST_PRINT = 13; + private static final int REQUEST_LINK = 14; + private static final int REQUEST_DISCARD = 15; + private static final int REQUEST_SEND = 16; + private static final int REQUEST_REMOVE_ATTACHMENTS = 17; @Override public void onCreate(Bundle savedInstanceState) { @@ -1804,6 +1805,7 @@ public class FragmentCompose extends FragmentBase { menu.findItem(R.id.menu_answer_insert).setEnabled(state == State.LOADED); menu.findItem(R.id.menu_answer_create).setEnabled(state == State.LOADED); menu.findItem(R.id.menu_clear).setEnabled(state == State.LOADED); + menu.findItem(R.id.menu_print).setEnabled(state == State.LOADED); SpannableStringBuilder ssbZoom = new SpannableStringBuilder(getString(R.string.title_zoom)); ssbZoom.append(' '); @@ -1962,6 +1964,9 @@ public class FragmentCompose extends FragmentBase { } else if (itemId == R.id.menu_clear) { StyleHelper.apply(R.id.menu_clear, getViewLifecycleOwner(), null, etBody); return true; + } else if (itemId == R.id.menu_print) { + onMenuPrint(); + return true; } else if (itemId == R.id.menu_legend) { onMenuLegend(); return true; @@ -2381,6 +2386,66 @@ public class FragmentCompose extends FragmentBase { fragment.show(getParentFragmentManager(), "select:identity"); } + private void onMenuPrint() { + Bundle extras = new Bundle(); + extras.putBoolean("silent", true); + onAction(R.id.action_save, extras, "paragraph"); + + CharSequence selected = null; + int start = etBody.getSelectionStart(); + int end = etBody.getSelectionEnd(); + + if (start < 0) + start = 0; + if (end < 0) + end = 0; + + if (start != end) { + if (start > end) { + int tmp = start; + start = end; + end = tmp; + } + + selected = etBody.getText().subSequence(start, end); + } + + Bundle args = new Bundle(); + args.putLong("id", working); + args.putBoolean("headers", false); + args.putCharSequence("selected", selected); + args.putBoolean("draft", true); + + new SimpleTask() { + @Override + protected Void onExecute(Context context, Bundle args) throws Throwable { + // Do nothing: serialize + return null; + } + + @Override + protected void onExecuted(Bundle args, Void dummy) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); + if (prefs.getBoolean("print_html_confirmed", false)) { + Intent data = new Intent(); + data.putExtra("args", args); + onActivityResult(REQUEST_PRINT, RESULT_OK, data); + return; + } + + FragmentDialogPrint ask = new FragmentDialogPrint(); + ask.setArguments(args); + ask.setTargetFragment(FragmentCompose.this, REQUEST_PRINT); + ask.show(getParentFragmentManager(), "compose:print"); + } + + @Override + protected void onException(Bundle args, Throwable ex) { + Log.unexpectedError(getParentFragmentManager(), ex); + } + }.serial().execute(this, args, "compose:print"); + } + private void onOpenAi() { int start = etBody.getSelectionStart(); int end = etBody.getSelectionEnd(); @@ -3028,6 +3093,10 @@ public class FragmentCompose extends FragmentBase { if (resultCode == RESULT_OK && data != null) onSelectIdentity(data.getBundleExtra("args")); break; + case REQUEST_PRINT: + if (resultCode == RESULT_OK && data != null) + onPrint(data.getBundleExtra("args")); + break; case REQUEST_LINK: if (resultCode == RESULT_OK && data != null) onLinkSelected(data.getBundleExtra("args")); @@ -4527,6 +4596,10 @@ public class FragmentCompose extends FragmentBase { }.serial().execute(this, args, "select:identity"); } + private void onPrint(Bundle args) { + FragmentDialogPrint.print((ActivityBase) getActivity(), getParentFragmentManager(), args); + } + private void onLinkSelected(Bundle args) { String link = args.getString("link"); boolean image = args.getBoolean("image"); diff --git a/app/src/main/java/eu/faircode/email/FragmentDialogPrint.java b/app/src/main/java/eu/faircode/email/FragmentDialogPrint.java index af8413b774..542c582059 100644 --- a/app/src/main/java/eu/faircode/email/FragmentDialogPrint.java +++ b/app/src/main/java/eu/faircode/email/FragmentDialogPrint.java @@ -21,21 +21,54 @@ package eu.faircode.email; import android.app.Activity; import android.app.Dialog; +import android.content.ActivityNotFoundException; import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; import android.os.Bundle; +import android.print.PrintAttributes; +import android.print.PrintDocumentAdapter; +import android.print.PrintJob; +import android.print.PrintManager; +import android.text.Spanned; +import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; import android.widget.CheckBox; import android.widget.CompoundButton; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; import androidx.preference.PreferenceManager; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; + public class FragmentDialogPrint extends FragmentDialogBase { + private static WebView printWebView = null; + @NonNull @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { @@ -86,4 +119,276 @@ public class FragmentDialogPrint extends FragmentDialogBase { }) .create(); } + + static void print(ActivityBase activity, FragmentManager fm, Bundle args) { + if (activity == null) { + Log.w("Print no activity"); + return; + } + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity); + boolean print_html_header = prefs.getBoolean("print_html_header", true); + boolean print_html_images = prefs.getBoolean("print_html_images", true); + + args.putBoolean("print_html_header", print_html_header); + args.putBoolean("print_html_images", print_html_images); + + new SimpleTask() { + @Override + protected String[] onExecute(Context context, Bundle args) throws IOException { + long id = args.getLong("id"); + boolean headers = args.getBoolean("headers"); + boolean print_html_header = args.getBoolean("print_html_header"); + boolean print_html_images = args.getBoolean("print_html_images"); + CharSequence selected = args.getCharSequence("selected"); + boolean draft = args.getBoolean("draft"); + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + int timeout = prefs.getInt("timeout", ImageHelper.DOWNLOAD_TIMEOUT) * 1000; + + DB db = DB.getInstance(context); + EntityMessage message = db.message().getMessage(id); + if (message == null || !message.content) + return null; + + File file = message.getFile(context); + if (!file.exists()) + return null; + + List attachments = db.attachment().getAttachments(message.id); + if (attachments == null) + return null; + + Document document; + if (!TextUtils.isEmpty(selected) && selected instanceof Spanned) + document = JsoupEx.parse(HtmlHelper.toHtml((Spanned) selected, context)); + else + document = JsoupEx.parse(file); + + boolean monospaced_pre = prefs.getBoolean("monospaced_pre", false); + if (message.isPlainOnly() && monospaced_pre) + HtmlHelper.restorePre(document); + + HtmlHelper.markText(document); + + HtmlHelper.embedInlineImages(context, id, document, true); + + // onPageFinished will not be called if not all images can be loaded + File dir = new File(context.getFilesDir(), "images"); + List> futures = new ArrayList<>(); + Elements imgs = document.select("img"); + for (int i = 0; i < imgs.size(); i++) { + Element img = imgs.get(i); + String src = img.attr("src"); + if (src.startsWith("http:") || src.startsWith("https:")) { + final File out = new File(dir, id + "." + i + ".print"); + img.attr("src", "file:" + out.getAbsolutePath()); + + if (print_html_images) { + if (out.exists() && out.length() > 0) + continue; + } else { + out.delete(); + continue; + } + + futures.add(Helper.getDownloadTaskExecutor().submit(new Callable() { + @Override + public Void call() throws Exception { + try (OutputStream os = new FileOutputStream(out)) { + URL url = new URL(src); + Log.i("Caching url=" + url); + + HttpURLConnection connection = null; + try { + connection = ConnectionHelper.openConnectionUnsafe(context, src, timeout, timeout); + Helper.copy(connection.getInputStream(), os); + } finally { + if (connection != null) + connection.disconnect(); + } + } catch (Throwable ex) { + Log.w(ex); + } + + return null; + } + })); + } + } + + for (Future future : futures) + try { + future.get(); + } catch (Throwable ex) { + Log.w(ex); + } + + // @page WordSection1 {size:612.0pt 792.0pt; margin:70.85pt 70.85pt 70.85pt 70.85pt;} + // div.WordSection1 {page:WordSection1;} + //
+ + for (Element element : document.body().select("div[class]")) { + String clazz = element.attr("class"); + if (clazz.startsWith("WordSection")) + element.removeClass(clazz); + } + + if (print_html_header) { + Element header = document.createElement("p"); + + if (draft) { + Element div = document.createElement("div"); + div.attr("style", "text-align: center;"); + Element strong = document.createElement("strong"); + strong.text(context.getString(R.string.title_compose)); + strong.attr("style", "text-transform: uppercase;"); + div.appendChild(strong); + header.appendChild(div); + header.appendElement("hr"); + } + + if (message.from != null && message.from.length > 0) { + Element span = document.createElement("span"); + Element strong = document.createElement("strong"); + strong.text(context.getString(R.string.title_from)); + span.appendChild(strong); + span.appendText(" " + MessageHelper.formatAddresses(message.from)); + span.appendElement("br"); + header.appendChild(span); + } + + if (message.to != null && message.to.length > 0) { + Element span = document.createElement("span"); + Element strong = document.createElement("strong"); + strong.text(context.getString(R.string.title_to)); + span.appendChild(strong); + span.appendText(" " + MessageHelper.formatAddresses(message.to)); + span.appendElement("br"); + header.appendChild(span); + } + + if (message.cc != null && message.cc.length > 0) { + Element span = document.createElement("span"); + Element strong = document.createElement("strong"); + strong.text(context.getString(R.string.title_cc)); + span.appendChild(strong); + span.appendText(" " + MessageHelper.formatAddresses(message.cc)); + span.appendElement("br"); + header.appendChild(span); + } + + if (message.received != null && !draft) { + DateFormat DTF = Helper.getDateTimeInstance(context, SimpleDateFormat.LONG, SimpleDateFormat.LONG); + + Element span = document.createElement("span"); + Element strong = document.createElement("strong"); + strong.text(context.getString(R.string.title_received)); + span.appendChild(strong); + span.appendText(" " + DTF.format(message.received)); + span.appendElement("br"); + header.appendChild(span); + } + + for (EntityAttachment attachment : attachments) + if (attachment.isAttachment()) { + Element span = document.createElement("span"); + Element strong = document.createElement("strong"); + strong.text(context.getString(R.string.title_attachment)); + span.appendChild(strong); + if (!TextUtils.isEmpty(attachment.name)) + span.appendText(" " + attachment.name); + if (attachment.size != null) + span.appendText(" " + Helper.humanReadableByteCount(attachment.size)); + span.appendElement("br"); + header.appendChild(span); + } + + if (!TextUtils.isEmpty(message.subject)) { + Element span = document.createElement("span"); + span.appendText(message.subject); + span.appendElement("br"); + header.appendChild(span); + } + + if (headers && message.headers != null) { + header.appendElement("hr"); + Element pre = document.createElement("pre"); + pre.text(message.headers); + header.appendChild(pre); + } + + header.appendElement("hr").appendElement("br"); + + document.body().prependChild(header); + } + + return new String[]{message.subject, document.body().html()}; + } + + @Override + protected void onExecuted(Bundle args, final String[] data) { + if (data == null) { + Log.w("Print no data"); + return; + } + + final Context context = activity.getOriginalContext(); + boolean print_html_images = args.getBoolean("print_html_images"); + + // https://developer.android.com/training/printing/html-docs.html + printWebView = new WebView(context); + + WebSettings settings = printWebView.getSettings(); + settings.setUserAgentString(WebViewEx.getUserAgent(context, printWebView)); + settings.setLoadsImagesAutomatically(print_html_images); + settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); + settings.setAllowFileAccess(true); + + printWebView.setWebViewClient(new WebViewClient() { + public boolean shouldOverrideUrlLoading(WebView view, String url) { + return false; + } + + @Override + public void onPageFinished(WebView view, String url) { + Log.i("Print page finished"); + + try { + if (printWebView == null) { + Log.w("Print no view"); + return; + } + + PrintManager printManager = (PrintManager) context.getSystemService(Context.PRINT_SERVICE); + String jobName = activity.getString(R.string.app_name); + if (!TextUtils.isEmpty(data[0])) + jobName += " - " + data[0]; + + Log.i("Print queue job=" + jobName); + PrintDocumentAdapter adapter = printWebView.createPrintDocumentAdapter(jobName); + PrintJob job = printManager.print(jobName, adapter, new PrintAttributes.Builder().build()); + EntityLog.log(context, "Print queued job=" + job.getInfo()); + } catch (Throwable ex) { + try { + Log.unexpectedError(fm, ex, !(ex instanceof ActivityNotFoundException)); + } catch (IllegalStateException exex) { + ToastEx.makeText(context, Log.formatThrowable(ex), Toast.LENGTH_LONG).show(); + } + } finally { + printWebView = null; + } + } + }); + + Log.i("Print load data"); + printWebView.loadDataWithBaseURL("about:blank", data[1], "text/html", StandardCharsets.UTF_8.name(), null); + } + + @Override + protected void onException(Bundle args, Throwable ex) { + Log.unexpectedError(fm, ex); + } + }.execute(activity, args, "print"); + } } diff --git a/app/src/main/java/eu/faircode/email/FragmentMessages.java b/app/src/main/java/eu/faircode/email/FragmentMessages.java index c28c25fe26..96d0afc0f0 100644 --- a/app/src/main/java/eu/faircode/email/FragmentMessages.java +++ b/app/src/main/java/eu/faircode/email/FragmentMessages.java @@ -39,7 +39,6 @@ import android.Manifest; import android.animation.ObjectAnimator; import android.app.NotificationManager; import android.app.PendingIntent; -import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.ContentUris; @@ -71,10 +70,6 @@ import android.os.OperationCanceledException; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; -import android.print.PrintAttributes; -import android.print.PrintDocumentAdapter; -import android.print.PrintJob; -import android.print.PrintManager; import android.provider.CalendarContract; import android.provider.ContactsContract; import android.provider.Settings; @@ -85,7 +80,6 @@ import android.text.Layout; import android.text.Spannable; import android.text.SpannableString; import android.text.SpannableStringBuilder; -import android.text.Spanned; import android.text.TextUtils; import android.text.TextWatcher; import android.text.format.DateUtils; @@ -113,9 +107,7 @@ import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.TranslateAnimation; import android.view.inputmethod.EditorInfo; -import android.webkit.WebSettings; import android.webkit.WebView; -import android.webkit.WebViewClient; import android.widget.ImageButton; import android.widget.SeekBar; import android.widget.TextView; @@ -183,9 +175,6 @@ import org.bouncycastle.cms.jcajce.JceKeyTransRecipient; import org.bouncycastle.operator.DefaultAlgorithmNameFinder; import org.bouncycastle.util.Store; import org.json.JSONException; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.select.Elements; import org.openintents.openpgp.AutocryptPeerUpdate; import org.openintents.openpgp.OpenPgpError; import org.openintents.openpgp.OpenPgpSignatureResult; @@ -201,9 +190,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.Serializable; import java.math.BigInteger; -import java.net.HttpURLConnection; -import java.net.URL; -import java.nio.charset.StandardCharsets; import java.security.KeyStore; import java.security.PrivateKey; import java.security.cert.CertPathBuilder; @@ -237,9 +223,7 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Properties; -import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; import java.util.function.Consumer; import javax.mail.Address; @@ -336,8 +320,6 @@ public class FragmentMessages extends FragmentBase private int searchIndex = 0; private TextView searchView = null; - private WebView printWebView = null; - private boolean hide_toolbar; private boolean cards; private boolean dividers; @@ -9962,264 +9944,7 @@ public class FragmentMessages extends FragmentBase } private void onPrint(Bundle args) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); - boolean print_html_header = prefs.getBoolean("print_html_header", true); - boolean print_html_images = prefs.getBoolean("print_html_images", true); - - args.putBoolean("print_html_header", print_html_header); - args.putBoolean("print_html_images", print_html_images); - - new SimpleTask() { - @Override - protected String[] onExecute(Context context, Bundle args) throws IOException { - long id = args.getLong("id"); - boolean headers = args.getBoolean("headers"); - boolean print_html_header = args.getBoolean("print_html_header"); - boolean print_html_images = args.getBoolean("print_html_images"); - CharSequence selected = args.getCharSequence("selected"); - - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - int timeout = prefs.getInt("timeout", ImageHelper.DOWNLOAD_TIMEOUT) * 1000; - - DB db = DB.getInstance(context); - EntityMessage message = db.message().getMessage(id); - if (message == null || !message.content) - return null; - - File file = message.getFile(context); - if (!file.exists()) - return null; - - List attachments = db.attachment().getAttachments(message.id); - if (attachments == null) - return null; - - Document document; - if (!TextUtils.isEmpty(selected) && selected instanceof Spanned) - document = JsoupEx.parse(HtmlHelper.toHtml((Spanned) selected, context)); - else - document = JsoupEx.parse(file); - - boolean monospaced_pre = prefs.getBoolean("monospaced_pre", false); - if (message.isPlainOnly() && monospaced_pre) - HtmlHelper.restorePre(document); - - HtmlHelper.markText(document); - - HtmlHelper.embedInlineImages(context, id, document, true); - - // onPageFinished will not be called if not all images can be loaded - File dir = new File(context.getFilesDir(), "images"); - List> futures = new ArrayList<>(); - Elements imgs = document.select("img"); - for (int i = 0; i < imgs.size(); i++) { - Element img = imgs.get(i); - String src = img.attr("src"); - if (src.startsWith("http:") || src.startsWith("https:")) { - final File out = new File(dir, id + "." + i + ".print"); - img.attr("src", "file:" + out.getAbsolutePath()); - - if (print_html_images) { - if (out.exists() && out.length() > 0) - continue; - } else { - out.delete(); - continue; - } - - futures.add(Helper.getDownloadTaskExecutor().submit(new Callable() { - @Override - public Void call() throws Exception { - try (OutputStream os = new FileOutputStream(out)) { - URL url = new URL(src); - Log.i("Caching url=" + url); - - HttpURLConnection connection = null; - try { - connection = ConnectionHelper.openConnectionUnsafe(context, src, timeout, timeout); - Helper.copy(connection.getInputStream(), os); - } finally { - if (connection != null) - connection.disconnect(); - } - } catch (Throwable ex) { - Log.w(ex); - } - - return null; - } - })); - } - } - - for (Future future : futures) - try { - future.get(); - } catch (Throwable ex) { - Log.w(ex); - } - - // @page WordSection1 {size:612.0pt 792.0pt; margin:70.85pt 70.85pt 70.85pt 70.85pt;} - // div.WordSection1 {page:WordSection1;} - //
- - for (Element element : document.body().select("div[class]")) { - String clazz = element.attr("class"); - if (clazz.startsWith("WordSection")) - element.removeClass(clazz); - } - - if (print_html_header) { - Element header = document.createElement("p"); - - if (message.from != null && message.from.length > 0) { - Element span = document.createElement("span"); - Element strong = document.createElement("strong"); - strong.text(context.getString(R.string.title_from)); - span.appendChild(strong); - span.appendText(" " + MessageHelper.formatAddresses(message.from)); - span.appendElement("br"); - header.appendChild(span); - } - - if (message.to != null && message.to.length > 0) { - Element span = document.createElement("span"); - Element strong = document.createElement("strong"); - strong.text(context.getString(R.string.title_to)); - span.appendChild(strong); - span.appendText(" " + MessageHelper.formatAddresses(message.to)); - span.appendElement("br"); - header.appendChild(span); - } - - if (message.cc != null && message.cc.length > 0) { - Element span = document.createElement("span"); - Element strong = document.createElement("strong"); - strong.text(context.getString(R.string.title_cc)); - span.appendChild(strong); - span.appendText(" " + MessageHelper.formatAddresses(message.cc)); - span.appendElement("br"); - header.appendChild(span); - } - - if (message.received != null) { - DateFormat DTF = Helper.getDateTimeInstance(context, SimpleDateFormat.LONG, SimpleDateFormat.LONG); - - Element span = document.createElement("span"); - Element strong = document.createElement("strong"); - strong.text(context.getString(R.string.title_received)); - span.appendChild(strong); - span.appendText(" " + DTF.format(message.received)); - span.appendElement("br"); - header.appendChild(span); - } - - for (EntityAttachment attachment : attachments) - if (attachment.isAttachment()) { - Element span = document.createElement("span"); - Element strong = document.createElement("strong"); - strong.text(context.getString(R.string.title_attachment)); - span.appendChild(strong); - if (!TextUtils.isEmpty(attachment.name)) - span.appendText(" " + attachment.name); - if (attachment.size != null) - span.appendText(" " + Helper.humanReadableByteCount(attachment.size)); - span.appendElement("br"); - header.appendChild(span); - } - - if (!TextUtils.isEmpty(message.subject)) { - Element span = document.createElement("span"); - span.appendText(message.subject); - span.appendElement("br"); - header.appendChild(span); - } - - if (headers && message.headers != null) { - header.appendElement("hr"); - Element pre = document.createElement("pre"); - pre.text(message.headers); - header.appendChild(pre); - } - - header.appendElement("hr").appendElement("br"); - - document.body().prependChild(header); - } - - return new String[]{message.subject, document.body().html()}; - } - - @Override - protected void onExecuted(Bundle args, final String[] data) { - if (data == null) { - Log.w("Print no data"); - return; - } - - ActivityBase activity = (ActivityBase) getActivity(); - if (activity == null) { - Log.w("Print no activity"); - return; - } - - final Context context = activity.getOriginalContext(); - boolean print_html_images = args.getBoolean("print_html_images"); - - // https://developer.android.com/training/printing/html-docs.html - printWebView = new WebView(context); - - WebSettings settings = printWebView.getSettings(); - settings.setUserAgentString(WebViewEx.getUserAgent(context, printWebView)); - settings.setLoadsImagesAutomatically(print_html_images); - settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); - settings.setAllowFileAccess(true); - - printWebView.setWebViewClient(new WebViewClient() { - public boolean shouldOverrideUrlLoading(WebView view, String url) { - return false; - } - - @Override - public void onPageFinished(WebView view, String url) { - Log.i("Print page finished"); - - try { - if (printWebView == null) { - Log.w("Print no view"); - return; - } - - PrintManager printManager = (PrintManager) context.getSystemService(Context.PRINT_SERVICE); - String jobName = getString(R.string.app_name); - if (!TextUtils.isEmpty(data[0])) - jobName += " - " + data[0]; - - Log.i("Print queue job=" + jobName); - PrintDocumentAdapter adapter = printWebView.createPrintDocumentAdapter(jobName); - PrintJob job = printManager.print(jobName, adapter, new PrintAttributes.Builder().build()); - EntityLog.log(context, "Print queued job=" + job.getInfo()); - } catch (Throwable ex) { - try { - Log.unexpectedError(getParentFragmentManager(), ex, !(ex instanceof ActivityNotFoundException)); - } catch (IllegalStateException exex) { - ToastEx.makeText(context, Log.formatThrowable(ex), Toast.LENGTH_LONG).show(); - } - } finally { - printWebView = null; - } - } - }); - - Log.i("Print load data"); - printWebView.loadDataWithBaseURL("about:blank", data[1], "text/html", StandardCharsets.UTF_8.name(), null); - } - - @Override - protected void onException(Bundle args, Throwable ex) { - Log.unexpectedError(getParentFragmentManager(), ex); - } - }.execute(this, args, "message:print"); + FragmentDialogPrint.print((ActivityBase) getActivity(), getParentFragmentManager(), args); } private void onEmptyFolder(Bundle args) { diff --git a/app/src/main/java/eu/faircode/email/Helper.java b/app/src/main/java/eu/faircode/email/Helper.java index 3265e0648c..97168840bc 100644 --- a/app/src/main/java/eu/faircode/email/Helper.java +++ b/app/src/main/java/eu/faircode/email/Helper.java @@ -1688,6 +1688,12 @@ public class Helper { continue; } + if (instance instanceof FragmentDialogPrint && + WebView.class.isAssignableFrom(type)) { + Log.i(fname + " clear skip"); + continue; + } + if (View.class.isAssignableFrom(type) || Animator.class.isAssignableFrom(type) || Snackbar.class.isAssignableFrom(type) || diff --git a/app/src/main/res/menu/menu_compose.xml b/app/src/main/res/menu/menu_compose.xml index 796a51c25b..eb64607f8f 100644 --- a/app/src/main/res/menu/menu_compose.xml +++ b/app/src/main/res/menu/menu_compose.xml @@ -131,6 +131,12 @@ android:icon="@drawable/twotone_format_clear_24" android:title="@string/title_style_clear" app:showAsAction="never" /> + +