diff --git a/app/src/main/java/eu/faircode/email/ActivityEML.java b/app/src/main/java/eu/faircode/email/ActivityEML.java index cb147f9bba..ff2da43e86 100644 --- a/app/src/main/java/eu/faircode/email/ActivityEML.java +++ b/app/src/main/java/eu/faircode/email/ActivityEML.java @@ -139,7 +139,7 @@ public class ActivityEML extends ActivityBase { result.html = parts.getHtml(context); if (result.html != null) { - result.body = HtmlHelper.fromHtml(HtmlHelper.sanitize(context, result.html, false, false)); + result.body = HtmlHelper.fromHtml(HtmlHelper.sanitize(context, result.html, false)); if (result.html.length() > 100 * 1024) result.html = null; } diff --git a/app/src/main/java/eu/faircode/email/AdapterMessage.java b/app/src/main/java/eu/faircode/email/AdapterMessage.java index 54e15ac39c..92e4ac4e4b 100644 --- a/app/src/main/java/eu/faircode/email/AdapterMessage.java +++ b/app/src/main/java/eu/faircode/email/AdapterMessage.java @@ -100,7 +100,6 @@ import androidx.constraintlayout.widget.Group; import androidx.core.content.FileProvider; import androidx.core.graphics.ColorUtils; import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleObserver; import androidx.lifecycle.LifecycleOwner; @@ -203,6 +202,7 @@ public class AdapterMessage extends RecyclerView.Adapter>() { private int lastInlineImages = 0; @@ -1228,7 +1217,7 @@ public class AdapterMessage extends RecyclerView.Adapter() { + @Override + protected Object onExecute(final Context context, final Bundle args) throws IOException { + TupleMessageEx message = (TupleMessageEx) args.getSerializable("message"); + boolean show_full = args.getBoolean("show_full"); + boolean show_images = args.getBoolean("show_images"); + boolean show_quotes = args.getBoolean("show_quotes"); + int zoom = args.getInt("zoom"); + + if (message == null || !message.content) + return null; + + File file = message.getFile(context); + if (!file.exists()) + return null; + + String body = Helper.readText(file); + Document document = JsoupEx.parse(body); + + // Check for inline encryption + int begin = body.indexOf(Helper.PGP_BEGIN_MESSAGE); + int end = body.indexOf(Helper.PGP_END_MESSAGE); + args.putBoolean("iencrypted", begin >= 0 && begin < end); + + // Check for images + boolean has_images = false; + for (Element img : document.select("img")) { + if (inline) { + String src = img.attr("src"); + if (!src.startsWith("cid:")) { + has_images = true; + break; + } + } else { + has_images = true; + break; + } + } + args.putBoolean("has_images", has_images); + + if (show_full) { + HtmlHelper.removeViewportLimitations(document); + if (inline) + HtmlHelper.embedImages(context, message.id, document); + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + boolean disable_tracking = prefs.getBoolean("disable_tracking", true); + if (disable_tracking) + HtmlHelper.removeTrackingPixels(context, document); + + return document.html(); + } else { + // Collapse quotes + if (!show_quotes) { + for (Element quote : document.select("blockquote")) + quote.html("…"); + body = document.html(); + } + + // Cleanup message + String html = HtmlHelper.sanitize(context, body, show_images); + if (debug) { + Document format = JsoupEx.parse(html); + format.outputSettings().prettyPrint(true).outline(true).indentAmount(1); + String[] lines = format.html().split("\\r?\\n"); + for (int i = 0; i < lines.length; i++) + lines[i] = Html.escapeHtml(lines[i]); + html += "
" + TextUtils.join("
", lines) + "
"; + } + + Spanned spanned = HtmlHelper.fromHtml(html, new Html.ImageGetter() { + @Override + public Drawable getDrawable(String source) { + Drawable drawable = HtmlHelper.decodeImage(context, message.id, source, show_images, tvBody); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + if (drawable instanceof AnimatedImageDrawable) + ((AnimatedImageDrawable) drawable).start(); + } + + return drawable; + } + }, null); + + // Replace quote spans + SpannableStringBuilder builder = new SpannableStringBuilder(spanned); + QuoteSpan[] quoteSpans = builder.getSpans(0, builder.length(), QuoteSpan.class); + for (QuoteSpan quoteSpan : quoteSpans) { + builder.setSpan( + new StyledQuoteSpan(context, colorPrimary), + builder.getSpanStart(quoteSpan), + builder.getSpanEnd(quoteSpan), + builder.getSpanFlags(quoteSpan)); + builder.removeSpan(quoteSpan); + } + + // Make collapsed quotes clickable + if (!show_quotes) { + final int px = Helper.dp2pixels(context, 24 + (zoom) * 8); + + StyledQuoteSpan[] squotes = builder.getSpans(0, builder.length(), StyledQuoteSpan.class); + for (StyledQuoteSpan squote : squotes) + builder.setSpan(new DynamicDrawableSpan() { + @Override + public Drawable getDrawable() { + Drawable d = context.getDrawable(R.drawable.baseline_format_quote_24); + d.setTint(colorAccent); + d.setBounds(0, 0, px, px); + return d; + } + }, + builder.getSpanStart(squote), + builder.getSpanEnd(squote), + builder.getSpanFlags(squote)); + } + + return builder; + } + } + + @Override + protected void onExecuted(Bundle args, Object result) { + TupleMessageEx message = (TupleMessageEx) args.getSerializable("message"); + properties.setValue("iencrypted", message.id, args.getBoolean("iencrypted")); + + TupleMessageEx amessage = getMessage(); + if (amessage == null || !amessage.id.equals(message.id)) + return; + + boolean show_expanded = properties.getValue("expanded", message.id); + if (!show_expanded) + return; + + boolean has_images = args.getBoolean("has_images"); + boolean show_images = properties.getValue("images", message.id); + + if (result instanceof Spanned) { + tvBody.setText((Spanned) result); + tvBody.setTextIsSelectable(false); + tvBody.setTextIsSelectable(true); + tvBody.setMovementMethod(new TouchHandler(message)); + + ibFull.setImageResource(R.drawable.baseline_fullscreen_24); + } else if (result instanceof String) { + ((WebView) wvBody).loadDataWithBaseURL(null, (String) result, "text/html", "UTF-8", null); + + ibFull.setImageResource(R.drawable.baseline_fullscreen_exit_24); + } else + throw new IllegalStateException("Result=" + result); + + pbBody.setVisibility(View.GONE); + + // Show attachments/images + cowner.start(); + ibFull.setEnabled(hasWebView); + ibImages.setVisibility(has_images && !show_images ? View.VISIBLE : View.GONE); + } + + @Override + protected void onException(Bundle args, Throwable ex) { + Helper.unexpectedError(parentFragment.getFragmentManager(), ex); + } + }.execute(context, owner, args, "message:body"); + } + private void bindAttachments(final TupleMessageEx message, @Nullable List attachments) { if (attachments == null) attachments = new ArrayList<>(); @@ -1567,17 +1893,26 @@ public class AdapterMessage extends RecyclerView.Adapter 1 && textSize != 0 && gestureDetector != null) { - //Log.i("Gesture event=" + ev); + if (ev.getPointerCount() > 1) { view.getParent().requestDisallowInterceptTouchEvent(true); - gestureDetector.onTouchEvent(ev); - return true; + if (view.getId() == R.id.tvBody) { + gestureDetector.onTouchEvent(ev); + return true; + } else + return false; } else { view.getParent().requestDisallowInterceptTouchEvent(false); return false; } } + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { + TupleMessageEx message = getMessage(); + if (message != null) + properties.setHeight(message.id, bottom - top); + } + @Override public void onClick(View view) { final TupleMessageEx message = getMessage(); @@ -1612,10 +1947,10 @@ public class AdapterMessage extends RecyclerView.Adapter bodyTask = new SimpleTask() { - @Override - protected SpannableStringBuilder onExecute(final Context context, final Bundle args) throws IOException { - TupleMessageEx message = (TupleMessageEx) args.getSerializable("message"); - boolean show_images = args.getBoolean("show_images"); - boolean show_quotes = args.getBoolean("show_quotes"); - int zoom = args.getInt("zoom"); - - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - boolean text_color = prefs.getBoolean("text_color", true); - boolean inline = prefs.getBoolean("inline_images", false); - - if (message == null || !message.content) - return null; - - File file = message.getFile(context); - if (!file.exists()) - return null; - - String body = Helper.readText(file); - Document document = JsoupEx.parse(body); - - // Check for inline encryption - int begin = body.indexOf(Helper.PGP_BEGIN_MESSAGE); - int end = body.indexOf(Helper.PGP_END_MESSAGE); - args.putBoolean("iencrypted", begin >= 0 && begin < end); - - // Check for images - boolean has_images = false; - for (Element img : document.select("img")) { - if (inline) { - String src = img.attr("src"); - if (!src.startsWith("cid:")) { - has_images = true; - break; - } - } else { - has_images = true; - break; - } - } - args.putBoolean("has_images", has_images); - - // Collapse quotes - if (!show_quotes) { - for (Element quote : document.select("blockquote")) - quote.html("…"); - body = document.html(); - } - - // Cleanup message - String html = HtmlHelper.sanitize(context, body, text_color, show_images); - if (debug) { - Document format = JsoupEx.parse(html); - format.outputSettings().prettyPrint(true).outline(true).indentAmount(1); - String[] lines = format.html().split("\\r?\\n"); - for (int i = 0; i < lines.length; i++) - lines[i] = Html.escapeHtml(lines[i]); - html += "
" + TextUtils.join("
", lines) + "
"; - } - - Spanned spanned = HtmlHelper.fromHtml(html, new Html.ImageGetter() { - @Override - public Drawable getDrawable(String source) { - Drawable drawable = HtmlHelper.decodeImage(context, message.id, source, show_images, tvBody); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - if (drawable instanceof AnimatedImageDrawable) - ((AnimatedImageDrawable) drawable).start(); - } - - return drawable; - } - }, null); - - // Replace quote spans - SpannableStringBuilder builder = new SpannableStringBuilder(spanned); - QuoteSpan[] quoteSpans = builder.getSpans(0, builder.length(), QuoteSpan.class); - for (QuoteSpan quoteSpan : quoteSpans) { - builder.setSpan( - new StyledQuoteSpan(context, colorPrimary), - builder.getSpanStart(quoteSpan), - builder.getSpanEnd(quoteSpan), - builder.getSpanFlags(quoteSpan)); - builder.removeSpan(quoteSpan); - } - - // Make collapsed quotes clickable - if (!show_quotes) { - final int px = Helper.dp2pixels(context, 24 + (zoom) * 8); - - StyledQuoteSpan[] squotes = builder.getSpans(0, builder.length(), StyledQuoteSpan.class); - for (StyledQuoteSpan squote : squotes) - builder.setSpan(new DynamicDrawableSpan() { - @Override - public Drawable getDrawable() { - Drawable d = context.getDrawable(R.drawable.baseline_format_quote_24); - d.setTint(colorAccent); - d.setBounds(0, 0, px, px); - return d; - } - }, - builder.getSpanStart(squote), - builder.getSpanEnd(squote), - builder.getSpanFlags(squote)); - } - - return builder; - } - - @Override - protected void onExecuted(Bundle args, SpannableStringBuilder body) { - TupleMessageEx message = (TupleMessageEx) args.getSerializable("message"); - properties.setBody(message.id, body); - properties.setValue("iencrypted", message.id, args.getBoolean("iencrypted")); - - TupleMessageEx amessage = getMessage(); - if (amessage == null || !amessage.id.equals(message.id)) - return; - - boolean show_expanded = properties.getValue("expanded", message.id); - if (!show_expanded) - return; - - boolean has_images = args.getBoolean("has_images"); - boolean show_images = properties.getValue("images", message.id); - - ibFull.setVisibility(hasWebView ? View.VISIBLE : View.GONE); - ibImages.setVisibility(has_images && !show_images ? View.VISIBLE : View.GONE); - - tvBody.setText(body); - tvBody.setTextIsSelectable(false); - tvBody.setTextIsSelectable(true); - tvBody.setMovementMethod(new TouchHandler(message)); - - pbBody.setVisibility(View.GONE); - - // Show attachments/images - cowner.start(); - } - - @Override - protected void onException(Bundle args, Throwable ex) { - Helper.unexpectedError(parentFragment.getFragmentManager(), ex); - } - }; - private class TouchHandler extends ArrowKeyMovementMethod { private TupleMessageEx message; @@ -2864,7 +3040,7 @@ public class AdapterMessage extends RecyclerView.Adapter 0) { properties.setValue("quotes", message.id, true); - loadText(message); + bindBody(message); } } @@ -3323,6 +3499,7 @@ public class AdapterMessage extends RecyclerView.Adapter attachments); @@ -3929,8 +4020,6 @@ public class AdapterMessage extends RecyclerView.Adapter() { - @Override - protected void onPreExecute(Bundle args) { - webView.setVisibility(View.GONE); - pbWait.setVisibility(View.VISIBLE); - } - - @Override - protected void onPostExecute(Bundle args) { - pbWait.setVisibility(View.GONE); - } - - @Override - protected String onExecute(Context context, Bundle args) throws Throwable { - long id = args.getLong("id"); - - 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; - - String html = Helper.readText(file); - - Document doc = JsoupEx.parse(html); - HtmlHelper.removeViewportLimitations(doc); - HtmlHelper.embedImages(context, id, doc); - - return doc.html(); - } - - @Override - protected void onExecuted(Bundle args, String html) { - webView.loadDataWithBaseURL("", html, "text/html", "UTF-8", null); - webView.setVisibility(View.VISIBLE); - } - - @Override - protected void onException(Bundle args, Throwable ex) { - Helper.unexpectedError(getFragmentManager(), ex); - } - }.execute(this, getArguments(), "message:full"); - - return dialog; - } - } - public static class FragmentKeywordManage extends FragmentDialogBase { @NonNull @Override diff --git a/app/src/main/java/eu/faircode/email/EditTextCompose.java b/app/src/main/java/eu/faircode/email/EditTextCompose.java index 61a1837fb8..8b8e112a93 100644 --- a/app/src/main/java/eu/faircode/email/EditTextCompose.java +++ b/app/src/main/java/eu/faircode/email/EditTextCompose.java @@ -68,7 +68,7 @@ public class EditTextCompose extends AppCompatEditText { ClipData.Item item = clipboard.getPrimaryClip().getItemAt(0); String html = item.coerceToHtmlText(context); - html = HtmlHelper.sanitize(context, html, true, false); + html = HtmlHelper.sanitize(context, html, false); Spanned paste = HtmlHelper.fromHtml(html); int start = getSelectionStart(); diff --git a/app/src/main/java/eu/faircode/email/FragmentCompose.java b/app/src/main/java/eu/faircode/email/FragmentCompose.java index ba8d237d5f..8dc3c1286e 100644 --- a/app/src/main/java/eu/faircode/email/FragmentCompose.java +++ b/app/src/main/java/eu/faircode/email/FragmentCompose.java @@ -2026,7 +2026,6 @@ public class FragmentCompose extends FragmentBase { long answer = args.getLong("answer", -1); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - boolean text_color = prefs.getBoolean("text_color", true); boolean plain_only = prefs.getBoolean("plain_only", false); boolean encrypt_default = prefs.getBoolean("encrypt_default", false); boolean receipt_default = prefs.getBoolean("receipt_default", false); @@ -2096,7 +2095,7 @@ public class FragmentCompose extends FragmentBase { data.draft.subject = args.getString("subject", ""); body = args.getString("body", ""); if (!TextUtils.isEmpty(body)) - body = HtmlHelper.sanitize(context, body, text_color, false); + body = HtmlHelper.sanitize(context, body, false); if (answer > 0) { EntityAnswer a = db.answer().getAnswer(answer); @@ -2164,7 +2163,7 @@ public class FragmentCompose extends FragmentBase { data.draft.subject = ref.subject; if (ref.content) { String html = Helper.readText(ref.getFile(context)); - body = HtmlHelper.sanitize(context, html, text_color, true); + body = HtmlHelper.sanitize(context, html, true); } } else if ("list".equals(action)) { data.draft.subject = ref.subject; @@ -2411,7 +2410,7 @@ public class FragmentCompose extends FragmentBase { if (data.draft.content) { File file = data.draft.getFile(context); String html = Helper.readText(file); - html = HtmlHelper.sanitize(context, html, true, true); + html = HtmlHelper.sanitize(context, html, true); Helper.writeText(file, html); } else { if (data.draft.uid == null) @@ -3114,9 +3113,6 @@ public class FragmentCompose extends FragmentBase { final long id = args.getLong("id"); final boolean show_images = args.getBoolean("show_images", false); - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - boolean text_color = prefs.getBoolean("text_color", true); - int colorPrimary = Helper.resolveColor(context, R.attr.colorPrimary); DB db = DB.getInstance(context); @@ -3143,7 +3139,7 @@ public class FragmentCompose extends FragmentBase { Spanned spannedRef = null; File refFile = draft.getRefFile(context); if (refFile.exists()) { - String quote = HtmlHelper.sanitize(context, Helper.readText(refFile), text_color, show_images); + String quote = HtmlHelper.sanitize(context, Helper.readText(refFile), show_images); Spanned spannedQuote = HtmlHelper.fromHtml(quote, new Html.ImageGetter() { @Override diff --git a/app/src/main/java/eu/faircode/email/FragmentMessages.java b/app/src/main/java/eu/faircode/email/FragmentMessages.java index d5c0e83ea0..9e8abf2af3 100644 --- a/app/src/main/java/eu/faircode/email/FragmentMessages.java +++ b/app/src/main/java/eu/faircode/email/FragmentMessages.java @@ -48,7 +48,6 @@ import android.os.Parcelable; import android.print.PrintAttributes; import android.print.PrintDocumentAdapter; import android.print.PrintManager; -import android.text.Spanned; import android.text.TextUtils; import android.text.format.DateUtils; import android.util.LongSparseArray; @@ -232,7 +231,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. private boolean autoExpanded = true; private Map> values = new HashMap<>(); private LongSparseArray sizes = new LongSparseArray<>(); - private LongSparseArray bodies = new LongSparseArray<>(); + private LongSparseArray heights = new LongSparseArray<>(); private LongSparseArray> attachments = new LongSparseArray<>(); private LongSparseArray accountSwipes = new LongSparseArray<>(); @@ -258,8 +257,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. static final int REQUEST_PRINT = 17; private static final int REQUEST_SEARCH = 18; private static final int REQUEST_ACCOUNT = 19; - static final int REQUEST_MESSAGE_PROPERTY = 20; - private static final int REQUEST_EMPTY_FOLDER = 21; + private static final int REQUEST_EMPTY_FOLDER = 20; static final String ACTION_STORE_RAW = BuildConfig.APPLICATION_ID + ".STORE_RAW"; static final String ACTION_DECRYPT = BuildConfig.APPLICATION_ID + ".DECRYPT"; @@ -1253,16 +1251,13 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. } @Override - public void setBody(long id, Spanned value) { - if (value == null) - bodies.remove(id); - else - bodies.put(id, value); + public void setHeight(long id, int height) { + heights.put(id, height); } @Override - public Spanned getBody(long id) { - return bodies.get(id); + public int getHeight(long id, int defaultHeight) { + return heights.get(id, defaultHeight); } @Override @@ -1285,16 +1280,6 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. }); } - @Override - public void scrollBy(final int dx, final int dy) { - new Handler().post(new Runnable() { - @Override - public void run() { - rvMessage.scrollBy(dx, dy); - } - }); - } - @Override public void move(long id, String type) { Bundle args = new Bundle(); @@ -2187,6 +2172,15 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. outState.putLongArray("fair:sizes:keys", skeys); outState.putFloatArray("fair:sizes:values", svalues); + long[] hkeys = new long[heights.size()]; + int[] hvalues = new int[heights.size()]; + for (int i = 0; i < heights.size(); i++) { + hkeys[i] = heights.keyAt(i); + hvalues[i] = heights.valueAt(i); + } + outState.putLongArray("fair:heights:keys", hkeys); + outState.putIntArray("fair:heights:values", hvalues); + if (rvMessage != null) { Parcelable rv = rvMessage.getLayoutManager().onSaveInstanceState(); outState.putParcelable("fair:rv", rv); @@ -2221,6 +2215,12 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. for (int i = 0; i < skeys.length; i++) sizes.put(skeys[i], svalues[i]); + long[] hkeys = savedInstanceState.getLongArray("fair:heights:keys"); + int[] hvalues = savedInstanceState.getIntArray("fair:heights:values"); + + for (int i = 0; i < hkeys.length; i++) + heights.put(hkeys[i], hvalues[i]); + if (rvMessage != null) { Parcelable rv = savedInstanceState.getBundle("fair:rv"); rvMessage.getLayoutManager().onRestoreInstanceState(rv); @@ -2338,7 +2338,8 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. Log.i("Hidden id=" + id); for (String key : values.keySet()) values.get(key).remove(id); - bodies.remove(id); + sizes.remove(id); + heights.remove(id); attachments.remove(id); } updateExpanded(); @@ -3706,10 +3707,6 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. onMenuFolders(args.getLong("account")); } break; - case REQUEST_MESSAGE_PROPERTY: - if (resultCode == RESULT_OK) - onPropertySet(data.getBundleExtra("args")); - break; case REQUEST_EMPTY_FOLDER: if (resultCode == RESULT_OK) onEmptyFolder(data.getBundleExtra("args")); @@ -4440,14 +4437,6 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. }.execute(this, pargs, "message:print"); } - private void onPropertySet(Bundle args) { - long id = args.getLong("id"); - String name = args.getString("name"); - boolean value = args.getBoolean("value"); - Log.i("Set property " + name + "=" + value + " id=" + id); - iProperties.setValue(name, id, value); - } - private void onEmptyFolder(Bundle args) { new SimpleTask() { @Override diff --git a/app/src/main/java/eu/faircode/email/FragmentOptions.java b/app/src/main/java/eu/faircode/email/FragmentOptions.java index 4210a30cdd..4a8d233bb5 100644 --- a/app/src/main/java/eu/faircode/email/FragmentOptions.java +++ b/app/src/main/java/eu/faircode/email/FragmentOptions.java @@ -46,7 +46,7 @@ public class FragmentOptions extends FragmentBase { "flags", "flags_background", "preview", "preview_italic", "addresses", "attachments_alt", "contrast", "monospaced", "text_color", - "inline_images", "collapse_quotes", "autocontent", "seekbar", "actionbar", + "inline_images", "collapse_quotes", "seekbar", "actionbar", "autoscroll", "swipenav", "autoexpand", "autoclose", "onclose", "experiments", "debug", "biometrics" diff --git a/app/src/main/java/eu/faircode/email/FragmentOptionsDisplay.java b/app/src/main/java/eu/faircode/email/FragmentOptionsDisplay.java index 51ff5d27c0..321143c7cd 100644 --- a/app/src/main/java/eu/faircode/email/FragmentOptionsDisplay.java +++ b/app/src/main/java/eu/faircode/email/FragmentOptionsDisplay.java @@ -71,7 +71,6 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer private SwitchCompat swTextColor; private SwitchCompat swCollapseQuotes; private SwitchCompat swImagesInline; - private SwitchCompat swRemoteContent; private SwitchCompat swSeekbar; private SwitchCompat swActionbar; @@ -81,7 +80,7 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer "subject_top", "subject_italic", "subject_ellipsize", "flags", "flags_background", "preview", "preview_italic", "addresses", "attachments_alt", "contrast", "monospaced", "text_color", - "inline_images", "collapse_quotes", "autocontent", "seekbar", "actionbar", + "inline_images", "collapse_quotes", "seekbar", "actionbar", }; @Override @@ -121,7 +120,6 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer swTextColor = view.findViewById(R.id.swTextColor); swCollapseQuotes = view.findViewById(R.id.swCollapseQuotes); swImagesInline = view.findViewById(R.id.swImagesInline); - swRemoteContent = view.findViewById(R.id.swRemoteContent); swSeekbar = view.findViewById(R.id.swSeekbar); swActionbar = view.findViewById(R.id.swActionbar); @@ -341,13 +339,6 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer } }); - swRemoteContent.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { - prefs.edit().putBoolean("autocontent", checked).apply(); - } - }); - swSeekbar.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { @@ -451,7 +442,6 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer swTextColor.setChecked(prefs.getBoolean("text_color", true)); swCollapseQuotes.setChecked(prefs.getBoolean("collapse_quotes", false)); swImagesInline.setChecked(prefs.getBoolean("inline_images", false)); - swRemoteContent.setChecked(prefs.getBoolean("autocontent", false)); swSeekbar.setChecked(prefs.getBoolean("seekbar", false)); swActionbar.setChecked(prefs.getBoolean("actionbar", true)); } diff --git a/app/src/main/java/eu/faircode/email/FragmentOptionsMisc.java b/app/src/main/java/eu/faircode/email/FragmentOptionsMisc.java index 7111962750..53bcbfef56 100644 --- a/app/src/main/java/eu/faircode/email/FragmentOptionsMisc.java +++ b/app/src/main/java/eu/faircode/email/FragmentOptionsMisc.java @@ -255,7 +255,7 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc for (String option : RESET_QUESTIONS) editor.remove(option); for (String key : prefs.getAll().keySet()) - if (key.endsWith(".show_images")) + if (key.endsWith(".show_full") || key.endsWith(".show_images")) editor.remove(key); editor.apply(); ToastEx.makeText(getContext(), R.string.title_setup_done, Toast.LENGTH_LONG).show(); diff --git a/app/src/main/java/eu/faircode/email/HtmlHelper.java b/app/src/main/java/eu/faircode/email/HtmlHelper.java index b6f978ae82..c013928199 100644 --- a/app/src/main/java/eu/faircode/email/HtmlHelper.java +++ b/app/src/main/java/eu/faircode/email/HtmlHelper.java @@ -96,7 +96,11 @@ public class HtmlHelper { private static final ExecutorService executor = Executors.newSingleThreadExecutor(Helper.backgroundThreadFactory); - static String sanitize(Context context, String html, boolean text_color, boolean show_images) { + static String sanitize(Context context, String html, boolean show_images) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + boolean text_color = prefs.getBoolean("text_color", true); + boolean disable_tracking = prefs.getBoolean("disable_tracking", true); + Document parsed = JsoupEx.parse(html); // hosts = new ArrayList<>(); + // Remove tracking pixels if (disable_tracking) - for (Element img : document.select("img")) { - String src = img.attr("src"); - if (!TextUtils.isEmpty(src) && !isTrackingPixel(img)) { - Uri uri = Uri.parse(img.attr("src")); - String host = uri.getHost(); - if (host != null && !hosts.contains(host)) - hosts.add(host); - } - } + removeTrackingPixels(context, document); // Images for (Element img : document.select("img")) { - // Remove link tracking pixels - if (disable_tracking) { - String src = img.attr("src"); - if (!TextUtils.isEmpty(src) && isTrackingPixel(img)) { - Uri uri = Uri.parse(img.attr("src")); - String host = uri.getHost(); - if (host == null || !hosts.contains(host)) { - img.removeAttr("src"); - img.tagName("a"); - img.attr("href", src); - img.appendText(context.getString(R.string.title_hint_tracking_image, - img.attr("width"), img.attr("height"))); - } - } - } - if (!show_images) { String alt = img.attr("alt"); if (!TextUtils.isEmpty(alt)) { @@ -515,6 +491,36 @@ public class HtmlHelper { return false; } + static void removeTrackingPixels(Context context, Document document) { + // Build list of allowed hosts + List hosts = new ArrayList<>(); + for (Element img : document.select("img")) { + String src = img.attr("src"); + if (!TextUtils.isEmpty(src) && !isTrackingPixel(img)) { + Uri uri = Uri.parse(img.attr("src")); + String host = uri.getHost(); + if (host != null && !hosts.contains(host)) + hosts.add(host); + } + } + + // Images + for (Element img : document.select("img")) { + String src = img.attr("src"); + if (!TextUtils.isEmpty(src) && isTrackingPixel(img)) { + Uri uri = Uri.parse(img.attr("src")); + String host = uri.getHost(); + if (host == null || !hosts.contains(host)) { + img.removeAttr("src"); + img.tagName("a"); + img.attr("href", src); + img.appendText(context.getString(R.string.title_hint_tracking_image, + img.attr("width"), img.attr("height"))); + } + } + } + } + static Drawable decodeImage(final Context context, final long id, String source, boolean show, final TextView view) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); boolean compact = prefs.getBoolean("compact", false); diff --git a/app/src/main/res/drawable/baseline_fullscreen_exit_24.xml b/app/src/main/res/drawable/baseline_fullscreen_exit_24.xml new file mode 100644 index 0000000000..01b8d4f4c3 --- /dev/null +++ b/app/src/main/res/drawable/baseline_fullscreen_exit_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/dialog_show_full.xml b/app/src/main/res/layout/dialog_show_full.xml new file mode 100644 index 0000000000..a354ccce85 --- /dev/null +++ b/app/src/main/res/layout/dialog_show_full.xml @@ -0,0 +1,33 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_options_display.xml b/app/src/main/res/layout/fragment_options_display.xml index cf379fe74f..36e33d5fa5 100644 --- a/app/src/main/res/layout/fragment_options_display.xml +++ b/app/src/main/res/layout/fragment_options_display.xml @@ -427,17 +427,6 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/swImagesInline" /> - - + app:layout_constraintTop_toBottomOf="@id/tvImagesInlineHint" /> + + + app:layout_constraintTop_toBottomOf="@id/wvBody" /> Show text colors Collapse quoted text Automatically show inline images - Automatically show remote content when viewing original messages Show relative conversation position with a dot Show conversation action bar