mirror of https://github.com/M66B/FairEmail.git
Added draft message printing
This commit is contained in:
parent
c7fb523222
commit
9c29824738
|
@ -9,6 +9,7 @@ For support you can use [the contact form](https://contact.faircode.eu/?product=
|
||||||
### Next version
|
### Next version
|
||||||
|
|
||||||
* Added linking contacts to identities to attach vCards
|
* Added linking contacts to identities to attach vCards
|
||||||
|
* Added draft message printing
|
||||||
* Small improvements and minor bug fixes
|
* Small improvements and minor bug fixes
|
||||||
* Updated AndroidX
|
* Updated AndroidX
|
||||||
* Updated [translations](https://crowdin.com/project/open-source-email)
|
* Updated [translations](https://crowdin.com/project/open-source-email)
|
||||||
|
|
|
@ -9,6 +9,7 @@ For support you can use [the contact form](https://contact.faircode.eu/?product=
|
||||||
### Next version
|
### Next version
|
||||||
|
|
||||||
* Added linking contacts to identities to attach vCards
|
* Added linking contacts to identities to attach vCards
|
||||||
|
* Added draft message printing
|
||||||
* Small improvements and minor bug fixes
|
* Small improvements and minor bug fixes
|
||||||
* Updated AndroidX
|
* Updated AndroidX
|
||||||
* Updated [translations](https://crowdin.com/project/open-source-email)
|
* Updated [translations](https://crowdin.com/project/open-source-email)
|
||||||
|
|
|
@ -325,10 +325,11 @@ public class FragmentCompose extends FragmentBase {
|
||||||
private static final int REQUEST_OPENPGP = 10;
|
private static final int REQUEST_OPENPGP = 10;
|
||||||
private static final int REQUEST_CONTACT_GROUP = 11;
|
private static final int REQUEST_CONTACT_GROUP = 11;
|
||||||
private static final int REQUEST_SELECT_IDENTITY = 12;
|
private static final int REQUEST_SELECT_IDENTITY = 12;
|
||||||
private static final int REQUEST_LINK = 13;
|
private static final int REQUEST_PRINT = 13;
|
||||||
private static final int REQUEST_DISCARD = 14;
|
private static final int REQUEST_LINK = 14;
|
||||||
private static final int REQUEST_SEND = 15;
|
private static final int REQUEST_DISCARD = 15;
|
||||||
private static final int REQUEST_REMOVE_ATTACHMENTS = 16;
|
private static final int REQUEST_SEND = 16;
|
||||||
|
private static final int REQUEST_REMOVE_ATTACHMENTS = 17;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
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_insert).setEnabled(state == State.LOADED);
|
||||||
menu.findItem(R.id.menu_answer_create).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_clear).setEnabled(state == State.LOADED);
|
||||||
|
menu.findItem(R.id.menu_print).setEnabled(state == State.LOADED);
|
||||||
|
|
||||||
SpannableStringBuilder ssbZoom = new SpannableStringBuilder(getString(R.string.title_zoom));
|
SpannableStringBuilder ssbZoom = new SpannableStringBuilder(getString(R.string.title_zoom));
|
||||||
ssbZoom.append(' ');
|
ssbZoom.append(' ');
|
||||||
|
@ -1962,6 +1964,9 @@ public class FragmentCompose extends FragmentBase {
|
||||||
} else if (itemId == R.id.menu_clear) {
|
} else if (itemId == R.id.menu_clear) {
|
||||||
StyleHelper.apply(R.id.menu_clear, getViewLifecycleOwner(), null, etBody);
|
StyleHelper.apply(R.id.menu_clear, getViewLifecycleOwner(), null, etBody);
|
||||||
return true;
|
return true;
|
||||||
|
} else if (itemId == R.id.menu_print) {
|
||||||
|
onMenuPrint();
|
||||||
|
return true;
|
||||||
} else if (itemId == R.id.menu_legend) {
|
} else if (itemId == R.id.menu_legend) {
|
||||||
onMenuLegend();
|
onMenuLegend();
|
||||||
return true;
|
return true;
|
||||||
|
@ -2381,6 +2386,66 @@ public class FragmentCompose extends FragmentBase {
|
||||||
fragment.show(getParentFragmentManager(), "select:identity");
|
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<Void>() {
|
||||||
|
@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() {
|
private void onOpenAi() {
|
||||||
int start = etBody.getSelectionStart();
|
int start = etBody.getSelectionStart();
|
||||||
int end = etBody.getSelectionEnd();
|
int end = etBody.getSelectionEnd();
|
||||||
|
@ -3028,6 +3093,10 @@ public class FragmentCompose extends FragmentBase {
|
||||||
if (resultCode == RESULT_OK && data != null)
|
if (resultCode == RESULT_OK && data != null)
|
||||||
onSelectIdentity(data.getBundleExtra("args"));
|
onSelectIdentity(data.getBundleExtra("args"));
|
||||||
break;
|
break;
|
||||||
|
case REQUEST_PRINT:
|
||||||
|
if (resultCode == RESULT_OK && data != null)
|
||||||
|
onPrint(data.getBundleExtra("args"));
|
||||||
|
break;
|
||||||
case REQUEST_LINK:
|
case REQUEST_LINK:
|
||||||
if (resultCode == RESULT_OK && data != null)
|
if (resultCode == RESULT_OK && data != null)
|
||||||
onLinkSelected(data.getBundleExtra("args"));
|
onLinkSelected(data.getBundleExtra("args"));
|
||||||
|
@ -4527,6 +4596,10 @@ public class FragmentCompose extends FragmentBase {
|
||||||
}.serial().execute(this, args, "select:identity");
|
}.serial().execute(this, args, "select:identity");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onPrint(Bundle args) {
|
||||||
|
FragmentDialogPrint.print((ActivityBase) getActivity(), getParentFragmentManager(), args);
|
||||||
|
}
|
||||||
|
|
||||||
private void onLinkSelected(Bundle args) {
|
private void onLinkSelected(Bundle args) {
|
||||||
String link = args.getString("link");
|
String link = args.getString("link");
|
||||||
boolean image = args.getBoolean("image");
|
boolean image = args.getBoolean("image");
|
||||||
|
|
|
@ -21,21 +21,54 @@ package eu.faircode.email;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
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.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.webkit.WebSettings;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
import android.webkit.WebViewClient;
|
||||||
import android.widget.CheckBox;
|
import android.widget.CheckBox;
|
||||||
import android.widget.CompoundButton;
|
import android.widget.CompoundButton;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.preference.PreferenceManager;
|
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 {
|
public class FragmentDialogPrint extends FragmentDialogBase {
|
||||||
|
private static WebView printWebView = null;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||||
|
@ -86,4 +119,276 @@ public class FragmentDialogPrint extends FragmentDialogBase {
|
||||||
})
|
})
|
||||||
.create();
|
.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<String[]>() {
|
||||||
|
@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<EntityAttachment> 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<Future<Void>> 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<Void>() {
|
||||||
|
@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<Void> 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;}
|
||||||
|
// <body><div class=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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,6 @@ import android.Manifest;
|
||||||
import android.animation.ObjectAnimator;
|
import android.animation.ObjectAnimator;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.ActivityNotFoundException;
|
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.ContentUris;
|
import android.content.ContentUris;
|
||||||
|
@ -71,10 +70,6 @@ import android.os.OperationCanceledException;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.os.SystemClock;
|
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.CalendarContract;
|
||||||
import android.provider.ContactsContract;
|
import android.provider.ContactsContract;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
|
@ -85,7 +80,6 @@ import android.text.Layout;
|
||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.Spanned;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
import android.text.format.DateUtils;
|
import android.text.format.DateUtils;
|
||||||
|
@ -113,9 +107,7 @@ import android.view.animation.Animation;
|
||||||
import android.view.animation.AnimationUtils;
|
import android.view.animation.AnimationUtils;
|
||||||
import android.view.animation.TranslateAnimation;
|
import android.view.animation.TranslateAnimation;
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
import android.webkit.WebSettings;
|
|
||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
import android.webkit.WebViewClient;
|
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.SeekBar;
|
import android.widget.SeekBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
@ -183,9 +175,6 @@ import org.bouncycastle.cms.jcajce.JceKeyTransRecipient;
|
||||||
import org.bouncycastle.operator.DefaultAlgorithmNameFinder;
|
import org.bouncycastle.operator.DefaultAlgorithmNameFinder;
|
||||||
import org.bouncycastle.util.Store;
|
import org.bouncycastle.util.Store;
|
||||||
import org.json.JSONException;
|
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.AutocryptPeerUpdate;
|
||||||
import org.openintents.openpgp.OpenPgpError;
|
import org.openintents.openpgp.OpenPgpError;
|
||||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||||
|
@ -201,9 +190,6 @@ import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.cert.CertPathBuilder;
|
import java.security.cert.CertPathBuilder;
|
||||||
|
@ -237,9 +223,7 @@ import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Future;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import javax.mail.Address;
|
import javax.mail.Address;
|
||||||
|
@ -336,8 +320,6 @@ public class FragmentMessages extends FragmentBase
|
||||||
private int searchIndex = 0;
|
private int searchIndex = 0;
|
||||||
private TextView searchView = null;
|
private TextView searchView = null;
|
||||||
|
|
||||||
private WebView printWebView = null;
|
|
||||||
|
|
||||||
private boolean hide_toolbar;
|
private boolean hide_toolbar;
|
||||||
private boolean cards;
|
private boolean cards;
|
||||||
private boolean dividers;
|
private boolean dividers;
|
||||||
|
@ -9962,264 +9944,7 @@ public class FragmentMessages extends FragmentBase
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onPrint(Bundle args) {
|
private void onPrint(Bundle args) {
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
|
FragmentDialogPrint.print((ActivityBase) getActivity(), getParentFragmentManager(), args);
|
||||||
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<String[]>() {
|
|
||||||
@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<EntityAttachment> 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<Future<Void>> 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<Void>() {
|
|
||||||
@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<Void> 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;}
|
|
||||||
// <body><div class=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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onEmptyFolder(Bundle args) {
|
private void onEmptyFolder(Bundle args) {
|
||||||
|
|
|
@ -1688,6 +1688,12 @@ public class Helper {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (instance instanceof FragmentDialogPrint &&
|
||||||
|
WebView.class.isAssignableFrom(type)) {
|
||||||
|
Log.i(fname + " clear skip");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (View.class.isAssignableFrom(type) ||
|
if (View.class.isAssignableFrom(type) ||
|
||||||
Animator.class.isAssignableFrom(type) ||
|
Animator.class.isAssignableFrom(type) ||
|
||||||
Snackbar.class.isAssignableFrom(type) ||
|
Snackbar.class.isAssignableFrom(type) ||
|
||||||
|
|
|
@ -131,6 +131,12 @@
|
||||||
android:icon="@drawable/twotone_format_clear_24"
|
android:icon="@drawable/twotone_format_clear_24"
|
||||||
android:title="@string/title_style_clear"
|
android:title="@string/title_style_clear"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/menu_print"
|
||||||
|
android:icon="@drawable/twotone_print_24"
|
||||||
|
android:title="@string/title_print"
|
||||||
|
app:showAsAction="never" />
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
<item
|
<item
|
||||||
|
|
|
@ -9,6 +9,7 @@ Rahonavis
|
||||||
Next version
|
Next version
|
||||||
|
|
||||||
* Added linking contacts to identities to attach vCards
|
* Added linking contacts to identities to attach vCards
|
||||||
|
* Added draft message printing
|
||||||
* Small improvements and minor bug fixes
|
* Small improvements and minor bug fixes
|
||||||
* Updated AndroidX
|
* Updated AndroidX
|
||||||
* Updated translations
|
* Updated translations
|
||||||
|
|
Loading…
Reference in New Issue