Progressively load images

pull/156/head
M66B 6 years ago
parent 06b85c916e
commit b5642e6019

@ -1637,7 +1637,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
if (show_html) if (show_html)
onShowHtmlConfirmed(message); onShowHtmlConfirmed(message);
else { else {
ibImages.setEnabled(false); ibImages.setVisibility(View.GONE);
bodyTask.execute(context, owner, args, "message:body"); bodyTask.execute(context, owner, args, "message:body");
} }
@ -1753,7 +1753,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
return HtmlHelper.fromHtml(html, new Html.ImageGetter() { return HtmlHelper.fromHtml(html, new Html.ImageGetter() {
@Override @Override
public Drawable getDrawable(String source) { public Drawable getDrawable(String source) {
Drawable image = HtmlHelper.decodeImage(source, context, message.id, show_images); Drawable image = HtmlHelper.decodeImage(source, message.id, show_images, tvBody);
float width = context.getResources().getDisplayMetrics().widthPixels - float width = context.getResources().getDisplayMetrics().widthPixels -
Helper.dp2pixels(context, 12); // margins Helper.dp2pixels(context, 12); // margins

@ -342,6 +342,7 @@ public class FragmentCompose extends FragmentBase {
ibReferenceImages.setOnClickListener(new View.OnClickListener() { ibReferenceImages.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
ibReferenceImages.setVisibility(View.GONE);
onReferenceImages(); onReferenceImages();
} }
}); });
@ -2818,7 +2819,7 @@ public class FragmentCompose extends FragmentBase {
new Html.ImageGetter() { new Html.ImageGetter() {
@Override @Override
public Drawable getDrawable(String source) { public Drawable getDrawable(String source) {
Drawable image = HtmlHelper.decodeImage(source, context, id, show_images); Drawable image = HtmlHelper.decodeImage(source, id, show_images, tvReference);
float width = context.getResources().getDisplayMetrics().widthPixels - float width = context.getResources().getDisplayMetrics().widthPixels -
Helper.dp2pixels(context, 12); // margins; Helper.dp2pixels(context, 12); // margins;

@ -21,15 +21,19 @@ package eu.faircode.email;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.graphics.drawable.LevelListDrawable;
import android.net.Uri; import android.net.Uri;
import android.os.Handler;
import android.text.Html; import android.text.Html;
import android.text.Spanned; import android.text.Spanned;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Base64; import android.util.Base64;
import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -61,6 +65,8 @@ import java.net.URL;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import static androidx.core.text.HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_LIST_ITEM; import static androidx.core.text.HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_LIST_ITEM;
@ -75,6 +81,8 @@ public class HtmlHelper {
private static final List<String> tails = Collections.unmodifiableList(Arrays.asList( private static final List<String> tails = Collections.unmodifiableList(Arrays.asList(
"h1", "h2", "h3", "h4", "h5", "h6", "p", "ol", "ul", "li")); "h1", "h2", "h3", "h4", "h5", "h6", "p", "ol", "ul", "li"));
private static final ExecutorService executor = Executors.newCachedThreadPool(Helper.backgroundThreadFactory);
static String removeTracking(Context context, String html) { static String removeTracking(Context context, String html) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean paranoid = prefs.getBoolean("paranoid", true); boolean paranoid = prefs.getBoolean("paranoid", true);
@ -298,15 +306,18 @@ public class HtmlHelper {
return (body == null ? "" : body.html()); return (body == null ? "" : body.html());
} }
static Drawable decodeImage(String source, Context context, long id, boolean show) { static Drawable decodeImage(final String source, long id, boolean show, final TextView view) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(view.getContext());
boolean compact = prefs.getBoolean("compact", false); boolean compact = prefs.getBoolean("compact", false);
int zoom = prefs.getInt("zoom", compact ? 0 : 1); int zoom = prefs.getInt("zoom", compact ? 0 : 1);
int px = Helper.dp2pixels(context, (zoom + 1) * 24); final int px = Helper.dp2pixels(view.getContext(), (zoom + 1) * 24);
final Resources.Theme theme = view.getContext().getTheme();
final Resources res = view.getContext().getResources();
if (TextUtils.isEmpty(source)) { if (TextUtils.isEmpty(source)) {
Drawable d = context.getResources().getDrawable(R.drawable.baseline_broken_image_24, context.getTheme()); Drawable d = res.getDrawable(R.drawable.baseline_broken_image_24, theme);
d.setBounds(0, 0, px, px); d.setBounds(0, 0, px, px);
return d; return d;
} }
@ -320,33 +331,33 @@ public class HtmlHelper {
if (!show) { if (!show) {
// Show placeholder icon // Show placeholder icon
int resid = (embedded || data ? R.drawable.baseline_photo_library_24 : R.drawable.baseline_image_24); int resid = (embedded || data ? R.drawable.baseline_photo_library_24 : R.drawable.baseline_image_24);
Drawable d = context.getResources().getDrawable(resid, context.getTheme()); Drawable d = res.getDrawable(resid, theme);
d.setBounds(0, 0, px, px); d.setBounds(0, 0, px, px);
return d; return d;
} }
// Embedded images // Embedded images
if (embedded) { if (embedded) {
DB db = DB.getInstance(context); DB db = DB.getInstance(view.getContext());
String cid = "<" + source.substring(4) + ">"; String cid = "<" + source.substring(4) + ">";
EntityAttachment attachment = db.attachment().getAttachment(id, cid); EntityAttachment attachment = db.attachment().getAttachment(id, cid);
if (attachment == null) { if (attachment == null) {
Drawable d = context.getResources().getDrawable(R.drawable.baseline_broken_image_24, context.getTheme()); Drawable d = res.getDrawable(R.drawable.baseline_broken_image_24, theme);
d.setBounds(0, 0, px, px); d.setBounds(0, 0, px, px);
return d; return d;
} else if (!attachment.available) { } else if (!attachment.available) {
Drawable d = context.getResources().getDrawable(R.drawable.baseline_photo_library_24, context.getTheme()); Drawable d = res.getDrawable(R.drawable.baseline_photo_library_24, theme);
d.setBounds(0, 0, px, px); d.setBounds(0, 0, px, px);
return d; return d;
} else { } else {
Bitmap bm = Helper.decodeImage(attachment.getFile(context), Bitmap bm = Helper.decodeImage(attachment.getFile(view.getContext()),
context.getResources().getDisplayMetrics().widthPixels); res.getDisplayMetrics().widthPixels);
if (bm == null) { if (bm == null) {
Drawable d = context.getResources().getDrawable(R.drawable.baseline_broken_image_24, context.getTheme()); Drawable d = res.getDrawable(R.drawable.baseline_broken_image_24, theme);
d.setBounds(0, 0, px, px); d.setBounds(0, 0, px, px);
return d; return d;
} else { } else {
Drawable d = new BitmapDrawable(bm); Drawable d = new BitmapDrawable(res, bm);
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
return d; return d;
} }
@ -368,84 +379,110 @@ public class HtmlHelper {
if (bm == null) if (bm == null)
throw new IllegalArgumentException("decode byte array failed"); throw new IllegalArgumentException("decode byte array failed");
Drawable d = new BitmapDrawable(context.getResources(), bm); Drawable d = new BitmapDrawable(res, bm);
d.setBounds(0, 0, bm.getWidth(), bm.getHeight()); d.setBounds(0, 0, bm.getWidth(), bm.getHeight());
return d; return d;
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
Log.w(ex); Log.w(ex);
Drawable d = context.getResources().getDrawable(R.drawable.baseline_broken_image_24, context.getTheme()); Drawable d = res.getDrawable(R.drawable.baseline_broken_image_24, theme);
d.setBounds(0, 0, px, px); d.setBounds(0, 0, px, px);
return d; return d;
} }
// Get cache file name // Get cache file name
File dir = new File(context.getCacheDir(), "images"); File dir = new File(view.getContext().getCacheDir(), "images");
if (!dir.exists()) if (!dir.exists())
dir.mkdir(); dir.mkdir();
File file = new File(dir, id + "_" + Math.abs(source.hashCode()) + ".png"); final File file = new File(dir, id + "_" + Math.abs(source.hashCode()) + ".png");
if (file.exists()) { if (file.exists()) {
Log.i("Using cached " + file); Log.i("Using cached " + file);
Bitmap bm = BitmapFactory.decodeFile(file.getAbsolutePath()); Bitmap bm = BitmapFactory.decodeFile(file.getAbsolutePath());
if (bm == null) { if (bm == null) {
Drawable d = context.getResources().getDrawable(R.drawable.baseline_broken_image_24, context.getTheme()); Drawable d = res.getDrawable(R.drawable.baseline_broken_image_24, theme);
d.setBounds(0, 0, px, px); d.setBounds(0, 0, px, px);
return d; return d;
} else { } else {
Drawable d = new BitmapDrawable(bm); Drawable d = new BitmapDrawable(res, bm);
d.setBounds(0, 0, bm.getWidth(), bm.getHeight()); d.setBounds(0, 0, bm.getWidth(), bm.getHeight());
return d; return d;
} }
} }
try { final LevelListDrawable lld = new LevelListDrawable();
BitmapFactory.Options options = new BitmapFactory.Options(); Drawable wait = res.getDrawable(R.drawable.baseline_hourglass_empty_24, theme);
Log.i("Probe " + source); lld.addLevel(0, 0, wait);
try (InputStream probe = new URL(source).openStream()) { lld.setBounds(0, 0, px, px);
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(probe, null, options);
}
Log.i("Download " + source); final Handler handler = new Handler(view.getContext().getMainLooper());
Bitmap bm; executor.submit(new Runnable() {
try (InputStream is = new URL(source).openStream()) { @Override
int scaleTo = context.getResources().getDisplayMetrics().widthPixels; public void run() {
int factor = 1; try {
while (options.outWidth / factor > scaleTo) BitmapFactory.Options options = new BitmapFactory.Options();
factor *= 2; Log.i("Probe " + source);
try (InputStream probe = new URL(source).openStream()) {
if (factor > 1) { options.inJustDecodeBounds = true;
Log.i("Download image factor=" + factor); BitmapFactory.decodeStream(probe, null, options);
options.inJustDecodeBounds = false; }
options.inSampleSize = factor;
bm = BitmapFactory.decodeStream(is, null, options);
} else
bm = BitmapFactory.decodeStream(is);
}
if (bm == null) Log.i("Download " + source);
throw new FileNotFoundException("Download image failed"); Bitmap bm;
try (InputStream is = new URL(source).openStream()) {
int scaleTo = res.getDisplayMetrics().widthPixels;
int factor = 1;
while (options.outWidth / factor > scaleTo)
factor *= 2;
if (factor > 1) {
Log.i("Download image factor=" + factor);
options.inJustDecodeBounds = false;
options.inSampleSize = factor;
bm = BitmapFactory.decodeStream(is, null, options);
} else
bm = BitmapFactory.decodeStream(is);
}
Log.i("Downloaded image"); if (bm == null)
throw new FileNotFoundException("Download image failed");
try (OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) { Log.i("Downloaded image");
bm.compress(Bitmap.CompressFormat.PNG, 90, os);
try (OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) {
bm.compress(Bitmap.CompressFormat.PNG, 90, os);
}
// Create drawable from bitmap
Drawable d = new BitmapDrawable(res, bm);
d.setBounds(0, 0, bm.getWidth(), bm.getHeight());
post(d, source);
} catch (Throwable ex) {
// Show warning icon
Log.w(ex);
int resid = (ex instanceof IOException && !(ex instanceof FileNotFoundException)
? R.drawable.baseline_cloud_off_24
: R.drawable.baseline_broken_image_24);
Drawable d = res.getDrawable(resid, theme);
d.setBounds(0, 0, px, px);
post(d, source);
}
} }
// Create drawable from bitmap private void post(final Drawable d, String source) {
Drawable d = new BitmapDrawable(context.getResources(), bm); Log.i("Posting image=" + source);
d.setBounds(0, 0, bm.getWidth(), bm.getHeight()); handler.post(new Runnable() {
return d; @Override
} catch (Throwable ex) { public void run() {
// Show warning icon lld.addLevel(1, 1, d);
Log.w(ex); lld.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
int res = (ex instanceof IOException && !(ex instanceof FileNotFoundException) lld.setLevel(1);
? R.drawable.baseline_cloud_off_24 view.setText(view.getText());
: R.drawable.baseline_broken_image_24); }
Drawable d = context.getResources().getDrawable(res, context.getTheme()); });
d.setBounds(0, 0, px, px); }
return d; });
}
return lld;
} }
static String getPreview(String body) { static String getPreview(String body) {

Loading…
Cancel
Save