From b652db3e09117d9ff1379caba676d29282767ac8 Mon Sep 17 00:00:00 2001 From: M66B Date: Fri, 24 Aug 2018 19:58:52 +0000 Subject: [PATCH] Show images inline --- .../eu/faircode/email/FragmentMessage.java | 87 +++++++++++++++++-- .../java/eu/faircode/email/HtmlHelper.java | 1 + .../main/res/drawable/baseline_image_24.xml | 10 +++ .../main/res/drawable/baseline_warning_24.xml | 10 +++ app/src/main/res/layout/fragment_message.xml | 21 ++++- app/src/main/res/values/strings.xml | 1 + 6 files changed, 122 insertions(+), 8 deletions(-) create mode 100644 app/src/main/res/drawable/baseline_image_24.xml create mode 100644 app/src/main/res/drawable/baseline_warning_24.xml diff --git a/app/src/main/java/eu/faircode/email/FragmentMessage.java b/app/src/main/java/eu/faircode/email/FragmentMessage.java index 31fb793bfd..3cd0db70d8 100644 --- a/app/src/main/java/eu/faircode/email/FragmentMessage.java +++ b/app/src/main/java/eu/faircode/email/FragmentMessage.java @@ -23,16 +23,22 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.Typeface; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.preference.PreferenceManager; +import android.text.Editable; import android.text.Html; import android.text.Layout; import android.text.Spannable; import android.text.Spanned; import android.text.method.LinkMovementMethod; import android.text.style.URLSpan; +import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -40,6 +46,7 @@ import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; @@ -47,6 +54,11 @@ import android.widget.Toast; import com.google.android.material.bottomnavigation.BottomNavigationView; import com.google.android.material.floatingactionbutton.FloatingActionButton; +import org.xml.sax.XMLReader; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; import java.text.Collator; import java.text.DateFormat; import java.text.SimpleDateFormat; @@ -83,6 +95,7 @@ public class FragmentMessage extends FragmentEx { private RecyclerView rvAttachment; private TextView tvError; private View vSeparatorBody; + private Button btnImages; private TextView tvBody; private ProgressBar pbBody; private FloatingActionButton fab; @@ -125,6 +138,7 @@ public class FragmentMessage extends FragmentEx { rvAttachment = view.findViewById(R.id.rvAttachment); tvError = view.findViewById(R.id.tvError); vSeparatorBody = view.findViewById(R.id.vSeparatorBody); + btnImages = view.findViewById(R.id.btnImages); tvBody = view.findViewById(R.id.tvBody); pbBody = view.findViewById(R.id.pbBody); fab = view.findViewById(R.id.fab); @@ -264,6 +278,7 @@ public class FragmentMessage extends FragmentEx { grpHeader.setVisibility(View.GONE); grpAddresses.setVisibility(View.GONE); grpAttachments.setVisibility(View.GONE); + btnImages.setVisibility(View.GONE); grpMessage.setVisibility(View.GONE); pbBody.setVisibility(View.GONE); bottom_navigation.setVisibility(View.GONE); @@ -353,28 +368,90 @@ public class FragmentMessage extends FragmentEx { if (tvBody.getTag() == null) { // Spanned text needs to be loaded after recreation too - Bundle args = new Bundle(); + final Bundle args = new Bundle(); args.putLong("id", message.id); + args.putBoolean("has_images", false); + args.putBoolean("show_images", false); pbBody.setVisibility(View.VISIBLE); - new SimpleTask() { + final SimpleTask bodyTask = new SimpleTask() { @Override - protected Spanned onLoad(Context context, Bundle args) throws Throwable { + protected Spanned onLoad(final Context context, final Bundle args) throws Throwable { + final boolean show_images = args.getBoolean("show_images"); String body = EntityMessage.read(context, args.getLong("id")); args.putInt("size", body.length()); - return Html.fromHtml(HtmlHelper.sanitize(getContext(), body, false)); + + return Html.fromHtml(HtmlHelper.sanitize(getContext(), body, false), new Html.ImageGetter() { + @Override + public Drawable getDrawable(String source) { + float scale = context.getResources().getDisplayMetrics().density; + int px = (int) (24 * scale + 0.5f); + + if (show_images) { + InputStream is = null; + try { + is = new URL(source).openStream(); + Bitmap bm = BitmapFactory.decodeStream(is); + if (bm == null) + throw new IllegalArgumentException(); + Drawable d = new BitmapDrawable(context.getResources(), bm); + d.setBounds(0, 0, bm.getWidth(), bm.getHeight()); + // TODO image caching? + return d; + } catch (Throwable ex) { + Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex)); + Drawable d = context.getResources().getDrawable(R.drawable.baseline_warning_24, context.getTheme()); + d.setBounds(0, 0, px, px); + return d; + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + Log.w(Helper.TAG, e + "\n" + Log.getStackTraceString(e)); + } + } + } + } else { + args.putBoolean("has_images", true); + Drawable d = context.getResources().getDrawable(R.drawable.baseline_image_24, context.getTheme()); + d.setBounds(0, 0, px, px); + return d; + } + } + }, new Html.TagHandler() { + @Override + public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) { + + } + }); } @Override protected void onLoaded(Bundle args, Spanned body) { + boolean has_images = args.getBoolean("has_images"); + boolean show_images = args.getBoolean("show_images"); tvSize.setText(Helper.humanReadableByteCount(args.getInt("size"), false)); tvBody.setText(body); tvBody.setTag(true); + btnImages.setVisibility(has_images && !show_images ? View.VISIBLE : View.GONE); grpMessage.setVisibility(View.VISIBLE); fab.setVisibility(free ? View.GONE : View.VISIBLE); pbBody.setVisibility(View.GONE); + + } + }; + + bodyTask.load(FragmentMessage.this, args); + + btnImages.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + v.setEnabled(false); + args.putBoolean("show_images", true); + bodyTask.load(FragmentMessage.this, args); } - }.load(FragmentMessage.this, args); + }); } int typeface = (message.ui_seen ? Typeface.NORMAL : Typeface.BOLD); diff --git a/app/src/main/java/eu/faircode/email/HtmlHelper.java b/app/src/main/java/eu/faircode/email/HtmlHelper.java index 6f689b807d..4d14737258 100644 --- a/app/src/main/java/eu/faircode/email/HtmlHelper.java +++ b/app/src/main/java/eu/faircode/email/HtmlHelper.java @@ -95,6 +95,7 @@ public class HtmlHelper implements NodeVisitor { alt = context.getString(R.string.title_image); alt = Html.escapeHtml(alt); sb.append(" ").append(String.format("%s [%d]", ref, alt, refs.size())); + sb.append("\"""); } } } diff --git a/app/src/main/res/drawable/baseline_image_24.xml b/app/src/main/res/drawable/baseline_image_24.xml new file mode 100644 index 0000000000..2c62a1a6e1 --- /dev/null +++ b/app/src/main/res/drawable/baseline_image_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/baseline_warning_24.xml b/app/src/main/res/drawable/baseline_warning_24.xml new file mode 100644 index 0000000000..c18093a7fc --- /dev/null +++ b/app/src/main/res/drawable/baseline_warning_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/fragment_message.xml b/app/src/main/res/layout/fragment_message.xml index 49a0009c35..c305030c4f 100644 --- a/app/src/main/res/layout/fragment_message.xml +++ b/app/src/main/res/layout/fragment_message.xml @@ -226,17 +226,32 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tvError" /> +