diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 0abecd057..41d01e6a4 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -51,6 +51,9 @@
android:name=".ui.activities.HelpActivity"
android:label="@string/settings" />
+
+
+ mScaleDetector.onTouchEvent(event)
+ bitmapMatrix.getValues(m)
+ val x = m[Matrix.MTRANS_X]
+ val y = m[Matrix.MTRANS_Y]
+ val curr = PointF(event.x, event.y)
+ when (event.action) {
+ MotionEvent.ACTION_DOWN -> {
+ last[event.x] = event.y
+ start.set(last)
+ mode = DRAG
+ }
+
+ MotionEvent.ACTION_POINTER_DOWN -> {
+ last[event.x] = event.y
+ start.set(last)
+ mode = ZOOM
+ }
+
+ MotionEvent.ACTION_MOVE -> //if the mode is ZOOM or
+ //if the mode is DRAG and already zoomed
+ if (mode == ZOOM || mode == DRAG && saveScale > minScale) {
+ var deltaX = curr.x - last.x // x difference
+ var deltaY = curr.y - last.y // y difference
+ val scaleWidth = (origWidth * saveScale).roundToInt()
+ .toFloat() // width after applying current scale
+ val scaleHeight = (origHeight * saveScale).roundToInt()
+ .toFloat() // height after applying current scale
+ //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 < width) {
+ deltaX = 0f
+ if (y + deltaY > 0) deltaY = -y else if (y + deltaY < -bottom) deltaY =
+ -(y + bottom)
+ } else if (scaleHeight < height) {
+ deltaY = 0f
+ if (x + deltaX > 0) deltaX = -x else if (x + deltaX < -right) deltaX =
+ -(x + right)
+ } else {
+ if (x + deltaX > 0) deltaX = -x else if (x + deltaX < -right) deltaX =
+ -(x + right)
+ if (y + deltaY > 0) deltaY = -y else if (y + deltaY < -bottom) deltaY =
+ -(y + bottom)
+ }
+ //move the image with the matrix
+ bitmapMatrix.postTranslate(deltaX, deltaY)
+ //set the last touch location to the current
+ last[curr.x] = curr.y
+ }
+
+ MotionEvent.ACTION_UP -> {
+ mode = NONE
+ val xDiff = abs(curr.x - start.x).toInt()
+ val yDiff = abs(curr.y - start.y).toInt()
+ if (xDiff < CLICK && yDiff < CLICK) performClick()
+ }
+
+ MotionEvent.ACTION_POINTER_UP -> mode = NONE
+ }
+ imageMatrix = bitmapMatrix
+ invalidate()
+ true
+ }
+ }
+
+ override fun setImageBitmap(bm: Bitmap) {
+ super.setImageBitmap(bm)
+ bmWidth = bm.width.toFloat()
+ bmHeight = bm.height.toFloat()
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ width = MeasureSpec.getSize(widthMeasureSpec).toFloat()
+ height = MeasureSpec.getSize(heightMeasureSpec).toFloat()
+ // Fit to screen.
+ val scaleX = width / bmWidth
+ val scaleY = height / bmHeight
+ val scale = scaleX.coerceAtMost(scaleY)
+ bitmapMatrix.setScale(scale, scale)
+ imageMatrix = bitmapMatrix
+ saveScale = 1f
+
+ // Center the image
+ redundantYSpace = height - scale * bmHeight
+ redundantXSpace = width - scale * bmWidth
+ redundantYSpace /= 2f
+ redundantXSpace /= 2f
+ bitmapMatrix.postTranslate(redundantXSpace, redundantYSpace)
+ origWidth = width - 2 * redundantXSpace
+ origHeight = height - 2 * redundantYSpace
+ right = width * saveScale - width - 2 * redundantXSpace * saveScale
+ bottom = height * saveScale - height - 2 * redundantYSpace * saveScale
+ imageMatrix = bitmapMatrix
+ }
+
+ private inner class ScaleListener : SimpleOnScaleGestureListener() {
+ override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {
+ mode = ZOOM
+ return true
+ }
+
+ override fun onScale(detector: ScaleGestureDetector): Boolean {
+ var mScaleFactor = detector.scaleFactor
+ val origScale = saveScale
+ saveScale *= mScaleFactor
+ if (saveScale > maxScale) {
+ saveScale = maxScale
+ mScaleFactor = maxScale / origScale
+ } else if (saveScale < minScale) {
+ saveScale = minScale
+ mScaleFactor = minScale / origScale
+ }
+ right = width * saveScale - width - 2 * redundantXSpace * saveScale
+ bottom = height * saveScale - height - 2 * redundantYSpace * saveScale
+ if (origWidth * saveScale <= width || origHeight * saveScale <= height) {
+ bitmapMatrix.postScale(mScaleFactor, mScaleFactor, width / 2, height / 2)
+ if (mScaleFactor < 1) {
+ bitmapMatrix.getValues(m)
+ val x = m[Matrix.MTRANS_X]
+ val y = m[Matrix.MTRANS_Y]
+
+ if ((origWidth * saveScale).roundToInt() < width) {
+ if (y < -bottom) bitmapMatrix.postTranslate(
+ 0f,
+ -(y + bottom)
+ ) else if (y > 0) bitmapMatrix.postTranslate(0f, -y)
+ } else {
+ if (x < -right) bitmapMatrix.postTranslate(
+ -(x + right),
+ 0f
+ ) else if (x > 0) bitmapMatrix.postTranslate(-x, 0f)
+ }
+ }
+ } else {
+ bitmapMatrix.postScale(mScaleFactor, mScaleFactor, detector.focusX, detector.focusY)
+ bitmapMatrix.getValues(m)
+ val x = m[Matrix.MTRANS_X]
+ val y = m[Matrix.MTRANS_Y]
+
+ if (mScaleFactor < 1) {
+ if (x < -right) bitmapMatrix.postTranslate(
+ -(x + right),
+ 0f
+ ) else if (x > 0) bitmapMatrix.postTranslate(-x, 0f)
+ if (y < -bottom) bitmapMatrix.postTranslate(
+ 0f,
+ -(y + bottom)
+ ) else if (y > 0) bitmapMatrix.postTranslate(0f, -y)
+ }
+ }
+ return true
+ }
+ }
+
+ companion object {
+ const val NONE = 0
+ const val DRAG = 1
+ const val ZOOM = 2
+ const val CLICK = 3
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_zoomable_image.xml b/app/src/main/res/layout/activity_zoomable_image.xml
new file mode 100644
index 000000000..ee826c44f
--- /dev/null
+++ b/app/src/main/res/layout/activity_zoomable_image.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
\ No newline at end of file