That fuck shit the fascists are using
1package org.tm.archive.components;
2
3import android.annotation.SuppressLint;
4import android.content.Context;
5import android.net.Uri;
6import android.util.AttributeSet;
7import android.view.MotionEvent;
8import android.view.View;
9import android.widget.FrameLayout;
10
11import androidx.annotation.NonNull;
12import androidx.exifinterface.media.ExifInterface;
13
14import com.bumptech.glide.RequestManager;
15import com.bumptech.glide.load.engine.DiskCacheStrategy;
16import com.bumptech.glide.request.target.Target;
17import com.davemorrissey.labs.subscaleview.ImageSource;
18import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
19import com.davemorrissey.labs.subscaleview.decoder.DecoderFactory;
20import com.github.chrisbanes.photoview.PhotoView;
21
22import org.signal.core.util.logging.Log;
23import org.tm.archive.R;
24import org.tm.archive.components.subsampling.AttachmentBitmapDecoder;
25import org.tm.archive.components.subsampling.AttachmentRegionDecoder;
26import org.tm.archive.mms.DecryptableStreamUriLoader.DecryptableUri;
27import org.tm.archive.mms.PartAuthority;
28import org.tm.archive.util.ActionRequestListener;
29import org.tm.archive.util.BitmapDecodingException;
30import org.tm.archive.util.BitmapUtil;
31import org.tm.archive.util.MediaUtil;
32import org.tm.archive.util.ViewUtil;
33import org.signal.core.util.concurrent.SimpleTask;
34
35import java.io.IOException;
36import java.io.InputStream;
37
38
39public class ZoomingImageView extends FrameLayout {
40
41 private static final String TAG = Log.tag(ZoomingImageView.class);
42
43 private static final int ZOOM_TRANSITION_DURATION = 300;
44
45 private static final float ZOOM_LEVEL_MIN = 1.0f;
46
47 private static final float LARGE_IMAGES_ZOOM_LEVEL_MID = 2.0f;
48 private static final float LARGE_IMAGES_ZOOM_LEVEL_MAX = 5.0f;
49
50 private static final float SMALL_IMAGES_ZOOM_LEVEL_MID = 3.0f;
51 private static final float SMALL_IMAGES_ZOOM_LEVEL_MAX = 8.0f;
52
53 private final PhotoView photoView;
54 private final SubsamplingScaleImageView subsamplingImageView;
55
56 public ZoomingImageView(Context context) {
57 this(context, null);
58 }
59
60 public ZoomingImageView(Context context, AttributeSet attrs) {
61 this(context, attrs, 0);
62 }
63
64 public ZoomingImageView(Context context, AttributeSet attrs, int defStyleAttr) {
65 super(context, attrs, defStyleAttr);
66
67 inflate(context, R.layout.zooming_image_view, this);
68
69 this.photoView = findViewById(R.id.image_view);
70 this.subsamplingImageView = findViewById(R.id.subsampling_image_view);
71
72 this.photoView.setZoomTransitionDuration(ZOOM_TRANSITION_DURATION);
73 this.photoView.setScaleLevels(ZOOM_LEVEL_MIN, SMALL_IMAGES_ZOOM_LEVEL_MID, SMALL_IMAGES_ZOOM_LEVEL_MAX);
74
75 this.subsamplingImageView.setDoubleTapZoomDuration(ZOOM_TRANSITION_DURATION);
76 this.subsamplingImageView.setDoubleTapZoomScale(LARGE_IMAGES_ZOOM_LEVEL_MID);
77 this.subsamplingImageView.setMaxScale(LARGE_IMAGES_ZOOM_LEVEL_MAX);
78
79 this.photoView.setOnClickListener(v -> ZoomingImageView.this.callOnClick());
80 this.subsamplingImageView.setOnClickListener(v -> ZoomingImageView.this.callOnClick());
81 }
82
83 @SuppressLint("StaticFieldLeak")
84 public void setImageUri(@NonNull RequestManager requestManager, @NonNull Uri uri, @NonNull String contentType, @NonNull Runnable onMediaReady)
85 {
86 final Context context = getContext();
87 final int maxTextureSize = BitmapUtil.getMaxTextureSize();
88
89 Log.i(TAG, "Max texture size: " + maxTextureSize);
90
91 SimpleTask.run(ViewUtil.getActivityLifecycle(this), () -> {
92 if (MediaUtil.isGif(contentType)) return null;
93
94 try {
95 InputStream inputStream = PartAuthority.getAttachmentStream(context, uri);
96 return BitmapUtil.getDimensions(inputStream);
97 } catch (IOException | BitmapDecodingException e) {
98 Log.w(TAG, e);
99 return null;
100 }
101 }, dimensions -> {
102 Log.i(TAG, "Dimensions: " + (dimensions == null ? "(null)" : dimensions.first + ", " + dimensions.second));
103
104 if (dimensions == null || (dimensions.first <= maxTextureSize && dimensions.second <= maxTextureSize)) {
105 Log.i(TAG, "Loading in standard image view...");
106 setImageViewUri(requestManager, uri, onMediaReady);
107 } else {
108 Log.i(TAG, "Loading in subsampling image view...");
109 setSubsamplingImageViewUri(uri);
110 onMediaReady.run();
111 }
112 });
113 }
114
115 private void setImageViewUri(@NonNull RequestManager requestManager, @NonNull Uri uri, @NonNull Runnable onMediaReady) {
116 photoView.setVisibility(View.VISIBLE);
117 subsamplingImageView.setVisibility(View.GONE);
118
119 requestManager.load(new DecryptableUri(uri))
120 .diskCacheStrategy(DiskCacheStrategy.NONE)
121 .dontTransform()
122 .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
123 .addListener(ActionRequestListener.onEither(onMediaReady))
124 .into(photoView);
125 }
126
127 private void setSubsamplingImageViewUri(@NonNull Uri uri) {
128 subsamplingImageView.setBitmapDecoderFactory(new AttachmentBitmapDecoderFactory());
129 subsamplingImageView.setRegionDecoderFactory(new AttachmentRegionDecoderFactory());
130
131 subsamplingImageView.setVisibility(View.VISIBLE);
132 photoView.setVisibility(View.GONE);
133
134 // We manually set the orientation ourselves because using
135 // SubsamplingScaleImageView.ORIENTATION_USE_EXIF is unreliable:
136 // https://github.com/signalapp/Signal-Android/issues/11732#issuecomment-963203545
137 try {
138 final InputStream inputStream = PartAuthority.getAttachmentStream(getContext(), uri);
139 final int orientation = BitmapUtil.getExifOrientation(new ExifInterface(inputStream));
140 inputStream.close();
141 if (orientation == ExifInterface.ORIENTATION_ROTATE_90) {
142 subsamplingImageView.setOrientation(SubsamplingScaleImageView.ORIENTATION_90);
143 } else if (orientation == ExifInterface.ORIENTATION_ROTATE_180) {
144 subsamplingImageView.setOrientation(SubsamplingScaleImageView.ORIENTATION_180);
145 } else if (orientation == ExifInterface.ORIENTATION_ROTATE_270) {
146 subsamplingImageView.setOrientation(SubsamplingScaleImageView.ORIENTATION_270);
147 } else {
148 subsamplingImageView.setOrientation(SubsamplingScaleImageView.ORIENTATION_0);
149 }
150 } catch (IOException e) {
151 Log.w(TAG, e);
152 }
153
154 subsamplingImageView.setImage(ImageSource.uri(uri));
155 }
156
157 public void cleanup() {
158 photoView.setImageDrawable(null);
159 subsamplingImageView.recycle();
160 }
161
162 private static class AttachmentBitmapDecoderFactory implements DecoderFactory<AttachmentBitmapDecoder> {
163 @Override
164 public AttachmentBitmapDecoder make() throws IllegalAccessException, InstantiationException {
165 return new AttachmentBitmapDecoder();
166 }
167 }
168
169 private static class AttachmentRegionDecoderFactory implements DecoderFactory<AttachmentRegionDecoder> {
170 @Override
171 public AttachmentRegionDecoder make() throws IllegalAccessException, InstantiationException {
172 return new AttachmentRegionDecoder();
173 }
174 }
175
176 @Override
177 public boolean onInterceptTouchEvent(MotionEvent event) {
178 getParent().requestDisallowInterceptTouchEvent(event.getPointerCount() > 1);
179 return false;
180 }
181}