mirror of
https://github.com/M66B/FairEmail.git
synced 2025-03-03 18:26:20 +00:00
Use dialog fragment to view images
This commit is contained in:
parent
edde5e8414
commit
7630a6edcb
6 changed files with 67 additions and 280 deletions
|
@ -181,7 +181,7 @@ FairEmail uses:
|
|||
* [ShortcutBadger](https://github.com/leolin310148/ShortcutBadger). Copyright 2014 Leo Lin. [Apache license](https://github.com/leolin310148/ShortcutBadger/blob/master/LICENSE).
|
||||
* [Bugsnag exception reporter for Android](https://github.com/bugsnag/bugsnag-android). Copyright (c) 2012 Bugsnag. [MIT License](https://github.com/bugsnag/bugsnag-android/blob/master/LICENSE.txt).
|
||||
* [biweekly](https://github.com/mangstadt/biweekly). Copyright (c) 2013-2018, Michael Angstadt. [BSD 2-Clause](https://github.com/mangstadt/biweekly/blob/master/LICENSE).
|
||||
* Source code snippets from Stack Overflow. [MIT License](https://meta.stackexchange.com/questions/271080/the-mit-license-clarity-on-using-code-on-stack-overflow-and-stack-exchange).
|
||||
* [PhotoView](https://github.com/chrisbanes/PhotoView). Copyright 2018 Chris Banes. [Apache License](https://github.com/chrisbanes/PhotoView/blob/master/LICENSE).
|
||||
|
||||
Error reporting is sponsored by:
|
||||
|
||||
|
|
|
@ -147,6 +147,7 @@ dependencies {
|
|||
def badge_version = "1.1.22"
|
||||
def bugsnag_version = "4.15.0"
|
||||
def biweekly_version = "0.6.3"
|
||||
def photoview_version = "2.3.0"
|
||||
|
||||
// https://developer.android.com/jetpack/androidx/releases/
|
||||
|
||||
|
@ -228,6 +229,9 @@ dependencies {
|
|||
exclude group: 'com.fasterxml.jackson.core', module: 'jackson-core'
|
||||
}
|
||||
|
||||
// https://github.com/chrisbanes/PhotoView
|
||||
implementation "com.github.chrisbanes:PhotoView:$photoview_version"
|
||||
|
||||
// git clone https://android.googlesource.com/platform/frameworks/opt/colorpicker
|
||||
implementation project(path: ':colorpicker')
|
||||
}
|
||||
|
|
|
@ -105,6 +105,7 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
|||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
|
||||
|
||||
import com.github.chrisbanes.photoview.PhotoView;
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
||||
import com.google.android.material.bottomnavigation.LabelVisibilityMode;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
@ -1930,7 +1931,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
Spanned spanned = HtmlHelper.fromHtml(html, new Html.ImageGetter() {
|
||||
@Override
|
||||
public Drawable getDrawable(String source) {
|
||||
Drawable image = HtmlHelper.decodeImage(source, message.id, show_images, tvBody);
|
||||
Drawable image = HtmlHelper.decodeImage(context, message.id, source, show_images, tvBody);
|
||||
|
||||
ConstraintLayout.LayoutParams params =
|
||||
(ConstraintLayout.LayoutParams) tvBody.getLayoutParams();
|
||||
|
@ -2069,9 +2070,12 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
}
|
||||
|
||||
ImageSpan[] image = buffer.getSpans(off, off, ImageSpan.class);
|
||||
if (image.length > 0 && image[0].getSource() != null) {
|
||||
onOpenImage(image[0].getDrawable());
|
||||
return true;
|
||||
if (image.length > 0) {
|
||||
String source = image[0].getSource();
|
||||
if (source != null) {
|
||||
onOpenImage(message.id, source);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
DynamicDrawableSpan[] ddss = buffer.getSpans(off, off, DynamicDrawableSpan.class);
|
||||
|
@ -2107,26 +2111,16 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
}
|
||||
}
|
||||
|
||||
private void onOpenImage(Drawable drawable) {
|
||||
ImageView pv = new ZoomableImageView(context);
|
||||
pv.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
|
||||
pv.setImageDrawable(drawable);
|
||||
private void onOpenImage(long id, String source) {
|
||||
Log.i("Viewing image source=" + source);
|
||||
|
||||
// TODO: dialog fragment
|
||||
final Dialog dialog = new Dialog(context, android.R.style.Theme_Black_NoTitleBar_Fullscreen);
|
||||
dialog.setContentView(pv);
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", id);
|
||||
args.putString("source", source);
|
||||
|
||||
owner.getLifecycle().addObserver(new LifecycleObserver() {
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
|
||||
public void onCreate() {
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
|
||||
public void onDestroyed() {
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
FragmentDialogImage fragment = new FragmentDialogImage();
|
||||
fragment.setArguments(args);
|
||||
fragment.show(parentFragment.getFragmentManager(), "view:image");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -3518,6 +3512,39 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
}
|
||||
}
|
||||
|
||||
public static class FragmentDialogImage extends DialogFragmentEx {
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
final PhotoView pv = new PhotoView(getContext());
|
||||
|
||||
new SimpleTask<Drawable>() {
|
||||
@Override
|
||||
protected Drawable onExecute(Context context, Bundle args) throws Throwable {
|
||||
long id = args.getLong("id");
|
||||
String source = args.getString("source");
|
||||
return HtmlHelper.decodeImage(context, id, source, true, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onExecuted(Bundle args, Drawable drawable) {
|
||||
pv.setImageDrawable(drawable);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onException(Bundle args, Throwable ex) {
|
||||
Helper.unexpectedError(getFragmentManager(), ex);
|
||||
}
|
||||
}.execute(getContext(), getActivity(), getArguments(), "view:image");
|
||||
|
||||
// TODO: dialog fragment
|
||||
final Dialog dialog = new Dialog(getContext(), android.R.style.Theme_Black_NoTitleBar_Fullscreen);
|
||||
dialog.setContentView(pv);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
}
|
||||
|
||||
public static class FragmentDialogFull extends DialogFragmentEx {
|
||||
@NonNull
|
||||
@Override
|
||||
|
|
|
@ -2854,7 +2854,7 @@ public class FragmentCompose extends FragmentBase {
|
|||
new Html.ImageGetter() {
|
||||
@Override
|
||||
public Drawable getDrawable(String source) {
|
||||
Drawable image = HtmlHelper.decodeImage(source, id, show_images, tvReference);
|
||||
Drawable image = HtmlHelper.decodeImage(context, id, source, show_images, tvReference);
|
||||
|
||||
ConstraintLayout.LayoutParams params =
|
||||
(ConstraintLayout.LayoutParams) tvReference.getLayoutParams();
|
||||
|
|
|
@ -314,16 +314,16 @@ public class HtmlHelper {
|
|||
return (body == null ? "" : body.html());
|
||||
}
|
||||
|
||||
static Drawable decodeImage(final String source, final long id, boolean show, final TextView view) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(view.getContext());
|
||||
static Drawable decodeImage(final Context context, final long id, final String source, boolean show, final TextView view) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
boolean compact = prefs.getBoolean("compact", false);
|
||||
int zoom = prefs.getInt("zoom", compact ? 0 : 1);
|
||||
boolean inline = prefs.getBoolean("inline_images", false);
|
||||
|
||||
final int px = Helper.dp2pixels(view.getContext(), (zoom + 1) * 24);
|
||||
final int px = Helper.dp2pixels(context, (zoom + 1) * 24);
|
||||
|
||||
final Resources.Theme theme = view.getContext().getTheme();
|
||||
final Resources res = view.getContext().getResources();
|
||||
final Resources.Theme theme = context.getTheme();
|
||||
final Resources res = context.getResources();
|
||||
|
||||
if (TextUtils.isEmpty(source)) {
|
||||
Drawable d = res.getDrawable(R.drawable.baseline_broken_image_24, theme);
|
||||
|
@ -348,7 +348,7 @@ public class HtmlHelper {
|
|||
|
||||
// Embedded images
|
||||
if (embedded) {
|
||||
DB db = DB.getInstance(view.getContext());
|
||||
DB db = DB.getInstance(context);
|
||||
String cid = "<" + source.substring(4) + ">";
|
||||
EntityAttachment attachment = db.attachment().getAttachment(id, cid);
|
||||
if (attachment == null) {
|
||||
|
@ -362,7 +362,7 @@ public class HtmlHelper {
|
|||
d.setBounds(0, 0, px, px);
|
||||
return d;
|
||||
} else {
|
||||
Bitmap bm = Helper.decodeImage(attachment.getFile(view.getContext()),
|
||||
Bitmap bm = Helper.decodeImage(attachment.getFile(context),
|
||||
res.getDisplayMetrics().widthPixels);
|
||||
if (bm == null) {
|
||||
Log.i("Image not decodable CID=" + cid);
|
||||
|
@ -380,7 +380,7 @@ public class HtmlHelper {
|
|||
// Data URI
|
||||
if (data)
|
||||
try {
|
||||
return getDataDrawable(source, res);
|
||||
return getDataDrawable(res, source);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
Log.w(ex);
|
||||
Drawable d = res.getDrawable(R.drawable.baseline_broken_image_24, theme);
|
||||
|
@ -389,13 +389,13 @@ public class HtmlHelper {
|
|||
}
|
||||
|
||||
// Get cache file name
|
||||
File dir = new File(view.getContext().getCacheDir(), "images");
|
||||
File dir = new File(context.getCacheDir(), "images");
|
||||
if (!dir.exists())
|
||||
dir.mkdir();
|
||||
final File file = new File(dir, id + "_" + Math.abs(source.hashCode()) + ".png");
|
||||
|
||||
Drawable cached = getCachedImage(view.getContext(), file);
|
||||
if (cached != null)
|
||||
Drawable cached = getCachedImage(context, file);
|
||||
if (cached != null || view == null)
|
||||
return cached;
|
||||
|
||||
final LevelListDrawable lld = new LevelListDrawable();
|
||||
|
@ -404,7 +404,6 @@ public class HtmlHelper {
|
|||
lld.setBounds(0, 0, px, px);
|
||||
lld.setLevel(1);
|
||||
|
||||
final Context context = view.getContext().getApplicationContext();
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
@ -493,7 +492,7 @@ public class HtmlHelper {
|
|||
return lld;
|
||||
}
|
||||
|
||||
private static Drawable getDataDrawable(String source, Resources res) {
|
||||
private static Drawable getDataDrawable(Resources res, String source) {
|
||||
// "<img src=\"data:image/png;base64,iVBORw0KGgoAAA" +
|
||||
// "ANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4" +
|
||||
// "//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU" +
|
||||
|
|
|
@ -1,243 +0,0 @@
|
|||
package eu.faircode.email;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ScaleGestureDetector;
|
||||
import android.widget.ImageView;
|
||||
|
||||
/**
|
||||
* Created by alex on 23/02/16.
|
||||
* Based on code posted by Nicolas Tyler here:
|
||||
* https://stackoverflow.com/questions/6650398/android-imageview-zoom-in-and-zoom-out
|
||||
*/
|
||||
public class ZoomableImageView extends ImageView {
|
||||
|
||||
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
|
||||
|
||||
@Override
|
||||
public boolean onScaleBegin(ScaleGestureDetector detector) {
|
||||
mode = ZOOM;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScale(ScaleGestureDetector detector) {
|
||||
float scaleFactor = detector.getScaleFactor();
|
||||
float newScale = saveScale * scaleFactor;
|
||||
if (newScale < maxScale && newScale > minScale) {
|
||||
saveScale = newScale;
|
||||
float width = getWidth();
|
||||
float height = getHeight();
|
||||
right = (originalBitmapWidth * saveScale) - width;
|
||||
bottom = (originalBitmapHeight * saveScale) - height;
|
||||
|
||||
float scaledBitmapWidth = originalBitmapWidth * saveScale;
|
||||
float scaledBitmapHeight = originalBitmapHeight * saveScale;
|
||||
|
||||
if (scaledBitmapWidth <= width || scaledBitmapHeight <= height) {
|
||||
matrix.postScale(scaleFactor, scaleFactor, width / 2, height / 2);
|
||||
} else {
|
||||
matrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static final int NONE = 0;
|
||||
static final int DRAG = 1;
|
||||
static final int ZOOM = 2;
|
||||
static final int CLICK = 3;
|
||||
|
||||
private int mode = NONE;
|
||||
|
||||
private Matrix matrix = new Matrix();
|
||||
|
||||
private PointF last = new PointF();
|
||||
private PointF start = new PointF();
|
||||
private float minScale = 0.5f;
|
||||
private float maxScale = 4f;
|
||||
private float[] m;
|
||||
|
||||
private float redundantXSpace, redundantYSpace;
|
||||
private float saveScale = 1f;
|
||||
private float right, bottom, originalBitmapWidth, originalBitmapHeight;
|
||||
|
||||
private ScaleGestureDetector mScaleDetector;
|
||||
|
||||
public ZoomableImageView(Context context) {
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public ZoomableImageView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public ZoomableImageView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
super.setClickable(true);
|
||||
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
|
||||
m = new float[9];
|
||||
setImageMatrix(matrix);
|
||||
setScaleType(ScaleType.MATRIX);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
int bmHeight = getBmHeight();
|
||||
int bmWidth = getBmWidth();
|
||||
|
||||
float width = getMeasuredWidth();
|
||||
float height = getMeasuredHeight();
|
||||
//Fit to screen.
|
||||
float scale = width > height ? height / bmHeight : width / bmWidth;
|
||||
|
||||
matrix.setScale(scale, scale);
|
||||
saveScale = 1f;
|
||||
|
||||
originalBitmapWidth = scale * bmWidth;
|
||||
originalBitmapHeight = scale * bmHeight;
|
||||
|
||||
// Center the image
|
||||
redundantYSpace = (height - originalBitmapHeight);
|
||||
redundantXSpace = (width - originalBitmapWidth);
|
||||
|
||||
matrix.postTranslate(redundantXSpace / 2, redundantYSpace / 2);
|
||||
|
||||
setImageMatrix(matrix);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
mScaleDetector.onTouchEvent(event);
|
||||
|
||||
matrix.getValues(m);
|
||||
float x = m[Matrix.MTRANS_X];
|
||||
float y = m[Matrix.MTRANS_Y];
|
||||
PointF curr = new PointF(event.getX(), event.getY());
|
||||
|
||||
switch (event.getAction()) {
|
||||
//when one finger is touching
|
||||
//set the mode to DRAG
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
last.set(event.getX(), event.getY());
|
||||
start.set(last);
|
||||
mode = DRAG;
|
||||
break;
|
||||
//when two fingers are touching
|
||||
//set the mode to ZOOM
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
last.set(event.getX(), event.getY());
|
||||
start.set(last);
|
||||
mode = ZOOM;
|
||||
break;
|
||||
//when a finger moves
|
||||
//If mode is applicable move image
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
//if the mode is ZOOM or
|
||||
//if the mode is DRAG and already zoomed
|
||||
if (mode == ZOOM || (mode == DRAG && saveScale > minScale)) {
|
||||
float deltaX = curr.x - last.x;// x difference
|
||||
float deltaY = curr.y - last.y;// y difference
|
||||
float scaleWidth = Math.round(originalBitmapWidth * saveScale);// width after applying current scale
|
||||
float scaleHeight = Math.round(originalBitmapHeight * saveScale);// height after applying current scale
|
||||
|
||||
boolean limitX = false;
|
||||
boolean limitY = false;
|
||||
|
||||
//if scaleWidth is smaller than the views width
|
||||
//in other words if the image width fits in the view
|
||||
//limit left and right movement
|
||||
if (scaleWidth < getWidth() && scaleHeight < getHeight()) {
|
||||
// don't do anything
|
||||
} else if (scaleWidth < getWidth()) {
|
||||
deltaX = 0;
|
||||
limitY = true;
|
||||
}
|
||||
//if scaleHeight is smaller than the views height
|
||||
//in other words if the image height fits in the view
|
||||
//limit up and down movement
|
||||
else if (scaleHeight < getHeight()) {
|
||||
deltaY = 0;
|
||||
limitX = true;
|
||||
}
|
||||
//if the image doesnt fit in the width or height
|
||||
//limit both up and down and left and right
|
||||
else {
|
||||
limitX = true;
|
||||
limitY = true;
|
||||
}
|
||||
|
||||
if (limitY) {
|
||||
if (y + deltaY > 0) {
|
||||
deltaY = -y;
|
||||
} else if (y + deltaY < -bottom) {
|
||||
deltaY = -(y + bottom);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (limitX) {
|
||||
if (x + deltaX > 0) {
|
||||
deltaX = -x;
|
||||
} else if (x + deltaX < -right) {
|
||||
deltaX = -(x + right);
|
||||
}
|
||||
|
||||
}
|
||||
//move the image with the matrix
|
||||
matrix.postTranslate(deltaX, deltaY);
|
||||
//set the last touch location to the current
|
||||
last.set(curr.x, curr.y);
|
||||
}
|
||||
break;
|
||||
//first finger is lifted
|
||||
case MotionEvent.ACTION_UP:
|
||||
mode = NONE;
|
||||
int xDiff = (int) Math.abs(curr.x - start.x);
|
||||
int yDiff = (int) Math.abs(curr.y - start.y);
|
||||
if (xDiff < CLICK && yDiff < CLICK)
|
||||
performClick();
|
||||
break;
|
||||
// second finger is lifted
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
mode = NONE;
|
||||
break;
|
||||
}
|
||||
setImageMatrix(matrix);
|
||||
invalidate();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setMaxZoom(float x) {
|
||||
maxScale = x;
|
||||
}
|
||||
|
||||
private int getBmWidth() {
|
||||
Drawable drawable = getDrawable();
|
||||
if (drawable != null) {
|
||||
return drawable.getIntrinsicWidth();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int getBmHeight() {
|
||||
Drawable drawable = getDrawable();
|
||||
if (drawable != null) {
|
||||
return drawable.getIntrinsicHeight();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue