Skip to content

Commit f56399f

Browse files
committed
idk, not working yet
1 parent 086ccca commit f56399f

1 file changed

Lines changed: 123 additions & 57 deletions

File tree

  • app/src/main/kotlin/com/darkrockstudios/app/securecamera/viewphoto
Lines changed: 123 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,158 @@
11
package com.darkrockstudios.app.securecamera.viewphoto
22

3+
import androidx.compose.animation.core.animateOffsetAsState
34
import androidx.compose.foundation.Image
45
import androidx.compose.foundation.gestures.detectTapGestures
56
import androidx.compose.foundation.gestures.detectTransformGestures
67
import androidx.compose.runtime.*
78
import androidx.compose.ui.Modifier
89
import androidx.compose.ui.draw.clipToBounds
910
import androidx.compose.ui.geometry.Offset
11+
import androidx.compose.ui.geometry.Size
1012
import androidx.compose.ui.graphics.ImageBitmap
1113
import androidx.compose.ui.graphics.graphicsLayer
14+
import androidx.compose.ui.input.pointer.PointerEventType
1215
import androidx.compose.ui.input.pointer.pointerInput
1316
import androidx.compose.ui.layout.ContentScale
17+
import androidx.compose.ui.layout.onSizeChanged
1418

1519
@Stable
1620
class ImageViewerState internal constructor(
17-
scale: Float = 1f,
18-
offset: Offset = Offset.Zero,
19-
val minScale: Float = 1f,
20-
val maxScale: Float = 5f,
21+
scale: Float = 1f,
22+
offset: Offset = Offset.Zero,
23+
val minScale: Float = 1f,
24+
val maxScale: Float = 5f,
2125
) {
22-
var scale by mutableStateOf(scale)
23-
var offset by mutableStateOf(offset)
26+
var scale by mutableStateOf(scale)
27+
var offset by mutableStateOf(offset)
28+
var isGestureInProgress by mutableStateOf(false)
29+
var containerSize by mutableStateOf(Size.Zero)
30+
var imageSize by mutableStateOf(Size.Zero)
2431

25-
fun reset() {
26-
scale = 1f
27-
offset = Offset.Zero
28-
}
32+
fun reset() {
33+
scale = 1f
34+
offset = Offset.Zero
35+
}
36+
37+
fun calculateConstrainedOffset(currentOffset: Offset, scale: Float): Offset {
38+
if (scale <= 1f) {
39+
return Offset.Zero
40+
}
41+
42+
val scaledImageWidth = imageSize.width * scale
43+
val scaledImageHeight = imageSize.height * scale
44+
45+
val maxX = maxOf(0f, scaledImageWidth - containerSize.width)
46+
val maxY = maxOf(0f, scaledImageHeight - containerSize.height)
47+
48+
// This allows panning the image so that its edge aligns with the container edge
49+
return Offset(
50+
x = currentOffset.x.coerceIn(-maxX / 2, maxX / 2),
51+
y = currentOffset.y.coerceIn(-maxY / 2, maxY / 2)
52+
)
53+
}
2954
}
3055

3156
/**
3257
* Call this from your Composable to keep the state across recompositions.
3358
*/
3459
@Composable
3560
fun rememberImageViewerState(
36-
initialScale: Float = 1f,
37-
initialOffset: Offset = Offset.Zero,
38-
minScale: Float = 1f,
39-
maxScale: Float = 5f,
61+
initialScale: Float = 1f,
62+
initialOffset: Offset = Offset.Zero,
63+
minScale: Float = 1f,
64+
maxScale: Float = 5f,
4065
): ImageViewerState = remember {
41-
ImageViewerState(
42-
scale = initialScale,
43-
offset = initialOffset,
44-
minScale = minScale,
45-
maxScale = maxScale,
46-
)
66+
ImageViewerState(
67+
scale = initialScale,
68+
offset = initialOffset,
69+
minScale = minScale,
70+
maxScale = maxScale,
71+
)
4772
}
4873

4974
/**
5075
* A zoomable / pannable image-viewer composable.
5176
*
52-
* @param bitmap The photo to display.
53-
* @param state State returned from [rememberImageViewerState].
54-
* @param modifier Extra modifiers (size, background, etc.).
77+
* @param bitmap The photo to display.
78+
* @param state State returned from [rememberImageViewerState].
79+
* @param modifier Extra modifiers (size, background, etc.).
5580
*/
5681
@Composable
5782
fun ImageViewer(
58-
bitmap: ImageBitmap,
59-
state: ImageViewerState,
60-
modifier: Modifier = Modifier,
83+
bitmap: ImageBitmap,
84+
state: ImageViewerState,
85+
modifier: Modifier = Modifier,
6186
) {
62-
Image(
63-
bitmap = bitmap,
64-
contentDescription = null,
65-
contentScale = ContentScale.Fit,
66-
modifier = modifier
67-
.clipToBounds()
68-
.pointerInput(state) {
69-
detectTransformGestures { _, pan, zoom, _ ->
70-
// ----- Zoom -----
71-
val newScale = (state.scale * zoom).coerceIn(state.minScale, state.maxScale)
72-
73-
// ----- Pan (don’t forget current scale) -----
74-
val newOffset = state.offset + pan
75-
76-
state.scale = newScale
77-
state.offset = newOffset
78-
}
79-
}
80-
.pointerInput(Unit) {
81-
detectTapGestures(
82-
onDoubleTap = { state.reset() }
83-
)
84-
}
85-
.graphicsLayer {
86-
scaleX = state.scale
87-
scaleY = state.scale
88-
translationX = state.offset.x
89-
translationY = state.offset.y
90-
}
91-
)
92-
}
87+
LaunchedEffect(bitmap) {
88+
state.imageSize = Size(bitmap.width.toFloat(), bitmap.height.toFloat())
89+
}
90+
91+
var isGestureInProgress by remember { mutableStateOf(false) }
92+
93+
// Animate offset when gesture ends
94+
val animatedOffset by animateOffsetAsState(
95+
targetValue = if (isGestureInProgress) {
96+
println("Animating, gesture in progress")
97+
state.offset
98+
} else {
99+
println("Animating, gesture NOT in progress")
100+
state.calculateConstrainedOffset(state.offset, state.scale)
101+
}
102+
) {
103+
state.offset = it
104+
}
105+
106+
state.isGestureInProgress = isGestureInProgress
107+
108+
Image(
109+
bitmap = bitmap,
110+
contentDescription = null,
111+
contentScale = ContentScale.Fit,
112+
modifier = modifier
113+
.clipToBounds()
114+
.onSizeChanged { size ->
115+
state.containerSize = Size(size.width.toFloat(), size.height.toFloat())
116+
}
117+
// Track touch events to detect when gesture starts and ends
118+
.pointerInput(Unit) {
119+
awaitPointerEventScope {
120+
while (true) {
121+
val event = awaitPointerEvent()
122+
when (event.type) {
123+
PointerEventType.Press -> {
124+
println("Press")
125+
isGestureInProgress = true
126+
}
127+
128+
PointerEventType.Release -> {
129+
println("Release")
130+
isGestureInProgress = false
131+
}
132+
}
133+
}
134+
}
135+
}
136+
// Handle transform gestures (pan and zoom)
137+
.pointerInput(state) {
138+
detectTransformGestures { _, pan, zoom, _ ->
139+
val newScale = (state.scale * zoom).coerceIn(state.minScale, state.maxScale)
140+
141+
state.scale = newScale
142+
state.offset = state.offset + pan
143+
}
144+
}
145+
.pointerInput(Unit) {
146+
detectTapGestures(
147+
onDoubleTap = { state.reset() }
148+
)
149+
}
150+
.graphicsLayer {
151+
scaleX = state.scale
152+
scaleY = state.scale
153+
154+
translationX = if (isGestureInProgress) state.offset.x else animatedOffset.x
155+
translationY = if (isGestureInProgress) state.offset.y else animatedOffset.y
156+
}
157+
)
158+
}

0 commit comments

Comments
 (0)