That fuck shit the fascists are using
at master 181 lines 7.2 kB view raw
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}