Skip to content

Commit e372b35

Browse files
committed
fix(camera): prevent possible fish eye effect after scanning a bill and returning to initial zoom
Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent 3008838 commit e372b35

2 files changed

Lines changed: 57 additions & 41 deletions

File tree

app/src/main/java/com/getcode/ui/utils/AnimationUtils.kt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.getcode.ui.utils
22

33
import androidx.compose.animation.*
44
import androidx.compose.animation.core.*
5+
import kotlin.math.pow
56

67

78
object AnimationUtils {
@@ -64,6 +65,32 @@ object AnimationUtils {
6465
ModalAnimationSpeed.Slow -> modalEnterSlow
6566
} togetherWith modalExit
6667
}
68+
69+
fun ease(
70+
value: Float,
71+
fromRange: ClosedFloatingPointRange<Float>,
72+
toRange: ClosedFloatingPointRange<Float>,
73+
easeIn: Boolean,
74+
easeOut: Boolean
75+
): Float {
76+
val normalizedValue = (value - fromRange.start) / (fromRange.endInclusive - fromRange.start)
77+
78+
val easedValue: Float = if (easeIn && easeOut) {
79+
if (normalizedValue < 0.5f) {
80+
4 * normalizedValue * normalizedValue * normalizedValue
81+
} else {
82+
1 - (-2 * normalizedValue + 2).toDouble().pow(3.0).toFloat() / 2
83+
}
84+
} else if (easeIn) {
85+
normalizedValue * normalizedValue * normalizedValue
86+
} else if (easeOut) {
87+
1 - (1 - normalizedValue).toDouble().pow(3.0).toFloat()
88+
} else {
89+
normalizedValue
90+
}
91+
92+
return easedValue * (toRange.endInclusive - toRange.start) + toRange.start
93+
}
6794
}
6895

6996
sealed class ModalAnimationSpeed(

app/src/main/java/com/getcode/view/main/camera/CameraGestureController.kt

Lines changed: 30 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import androidx.camera.core.CameraInfo
1111
import androidx.camera.core.FocusMeteringAction
1212
import androidx.camera.core.MeteringPoint
1313
import androidx.compose.ui.geometry.Offset
14+
import com.getcode.ui.utils.AnimationUtils
1415
import java.util.concurrent.TimeUnit
15-
import kotlin.math.pow
1616

1717
internal class CameraGestureController(
1818
context: Context,
@@ -25,9 +25,24 @@ internal class CameraGestureController(
2525
private val handler = Handler(Looper.getMainLooper())
2626
private var shouldIgnoreScroll = false
2727
private var resetIgnore: Runnable? = null
28-
private var initialZoomLevel = 0f
28+
private var initialZoomRatio = 0f
29+
private var initialZoomLevel = -1f
2930
private var accumulatedDelta = 0f
3031

32+
private val maxZoom: Float
33+
get() = maxZoomOrNull ?: 1f
34+
private val minZoom: Float
35+
get() = minZoomOrNull ?: 1f
36+
37+
private val maxZoomOrNull: Float?
38+
get() = cameraInfo.zoomState.value?.maxZoomRatio
39+
40+
private val minZoomOrNull: Float?
41+
get() = cameraInfo.zoomState.value?.minZoomRatio
42+
43+
private val currentZoom: Float
44+
get() = cameraInfo.zoomState.value?.zoomRatio ?: 1f
45+
3146
// Pinch-to-zoom gesture detector
3247
private val scaleGestureDetector = ScaleGestureDetector(
3348
context,
@@ -39,14 +54,13 @@ internal class CameraGestureController(
3954
}
4055

4156
override fun onScale(detector: ScaleGestureDetector): Boolean {
42-
val currentZoomRatio = cameraInfo.zoomState.value?.zoomRatio ?: 1f
4357
val delta = detector.scaleFactor
44-
val newZoomRatio = currentZoomRatio * delta
58+
val newZoomRatio = currentZoom * delta
4559

4660
// Clamp the new zoom ratio between the minimum and maximum zoom ratio
4761
val clampedZoomRatio = newZoomRatio.coerceIn(
48-
cameraInfo.zoomState.value?.minZoomRatio ?: 1f,
49-
cameraInfo.zoomState.value?.maxZoomRatio ?: currentZoomRatio
62+
minZoom,
63+
maxZoomOrNull ?: currentZoom
5064
)
5165

5266
// Apply the zoom to the camera control
@@ -55,7 +69,7 @@ internal class CameraGestureController(
5569
}
5670

5771
override fun onScaleEnd(detector: ScaleGestureDetector) {
58-
initialZoomLevel = cameraInfo.zoomState.value?.zoomRatio ?: 1f
72+
initialZoomRatio = currentZoom
5973
resetIgnore = Runnable { shouldIgnoreScroll = false }
6074
resetIgnore?.let { handler.postDelayed(it, 500) }
6175
}
@@ -66,7 +80,7 @@ internal class CameraGestureController(
6680
context,
6781
object : GestureDetector.OnGestureListener {
6882
override fun onDown(e: MotionEvent): Boolean {
69-
initialZoomLevel = cameraInfo.zoomState.value?.zoomRatio ?: 1f
83+
initialZoomRatio = currentZoom
7084
accumulatedDelta = 0f
7185
return true
7286
}
@@ -94,18 +108,15 @@ internal class CameraGestureController(
94108
accumulatedDelta - distanceY * 0.5f
95109
}
96110

97-
val zoomDelta = ease(
111+
val zoomDelta = AnimationUtils.ease(
98112
value = accumulatedDelta,
99113
fromRange = 0f..250f,
100114
toRange = 0f..10f,
101115
easeIn = true,
102116
easeOut = false
103117
)
104118

105-
val maxZoom = cameraInfo.zoomState.value?.maxZoomRatio ?: 1f
106-
val minZoom = cameraInfo.zoomState.value?.minZoomRatio ?: 1f
107-
108-
val newZoom = (initialZoomLevel + zoomDelta).coerceIn(minZoom, maxZoom)
119+
val newZoom = (initialZoomRatio + zoomDelta).coerceIn(minZoom, maxZoom)
109120
cameraControl.setZoomRatio(newZoom)
110121
}
111122
return true
@@ -126,12 +137,16 @@ internal class CameraGestureController(
126137

127138
fun onTouchEvent(event: MotionEvent) {
128139
if (gesturesEnabled) {
140+
if (initialZoomLevel == -1f) {
141+
initialZoomLevel = cameraInfo.zoomState.value?.linearZoom ?: 0f
142+
}
143+
129144
scaleGestureDetector.onTouchEvent(event)
130145
gestureDetector.onTouchEvent(event)
131146

132147
if (event.action == MotionEvent.ACTION_UP) {
133148
animateZoomReset(cameraInfo, cameraControl)
134-
initialZoomLevel = cameraInfo.zoomState.value?.zoomRatio ?: 1f
149+
initialZoomRatio = currentZoom
135150
}
136151
}
137152
}
@@ -149,7 +164,7 @@ internal class CameraGestureController(
149164
override fun run() {
150165
if (currentStep < maxSteps) {
151166
val newZoomLevel = currentZoomLevel - (decrement * currentStep)
152-
cameraControl?.setLinearZoom(newZoomLevel.coerceIn(0f, 1f))
167+
cameraControl?.setLinearZoom(newZoomLevel.coerceIn(initialZoomLevel, 1f))
153168
currentStep++
154169
handler.postDelayed(this, frameInterval)
155170
} else {
@@ -158,30 +173,4 @@ internal class CameraGestureController(
158173
}
159174
})
160175
}
161-
162-
private fun ease(
163-
value: Float,
164-
fromRange: ClosedFloatingPointRange<Float>,
165-
toRange: ClosedFloatingPointRange<Float>,
166-
easeIn: Boolean,
167-
easeOut: Boolean
168-
): Float {
169-
val normalizedValue = (value - fromRange.start) / (fromRange.endInclusive - fromRange.start)
170-
171-
val easedValue: Float = if (easeIn && easeOut) {
172-
if (normalizedValue < 0.5f) {
173-
4 * normalizedValue * normalizedValue * normalizedValue
174-
} else {
175-
1 - (-2 * normalizedValue + 2).toDouble().pow(3.0).toFloat() / 2
176-
}
177-
} else if (easeIn) {
178-
normalizedValue * normalizedValue * normalizedValue
179-
} else if (easeOut) {
180-
1 - (1 - normalizedValue).toDouble().pow(3.0).toFloat()
181-
} else {
182-
normalizedValue
183-
}
184-
185-
return easedValue * (toRange.endInclusive - toRange.start) + toRange.start
186-
}
187176
}

0 commit comments

Comments
 (0)