Skip to content

Commit 61a06c5

Browse files
committed
ImageViewer
1 parent 040dfb5 commit 61a06c5

File tree

3 files changed

+110
-26
lines changed

3 files changed

+110
-26
lines changed

app/build.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@ dependencies {
8787
implementation(libs.koin.androidx.compose)
8888
implementation(libs.koin.androidx.compose.navigation)
8989
implementation(libs.koin.core.coroutines)
90-
implementation(libs.imageviewer)
9190
implementation(libs.androidx.material.icons.extended)
9291
implementation(platform(libs.cryptography.bom))
9392
implementation(libs.cryptography.core)
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package com.darkrockstudios.app.securecamera.viewphoto
2+
3+
import androidx.compose.foundation.Image
4+
import androidx.compose.foundation.gestures.detectTapGestures
5+
import androidx.compose.foundation.gestures.detectTransformGestures
6+
import androidx.compose.runtime.*
7+
import androidx.compose.ui.Modifier
8+
import androidx.compose.ui.draw.clipToBounds
9+
import androidx.compose.ui.geometry.Offset
10+
import androidx.compose.ui.graphics.ImageBitmap
11+
import androidx.compose.ui.graphics.graphicsLayer
12+
import androidx.compose.ui.input.pointer.pointerInput
13+
import androidx.compose.ui.layout.ContentScale
14+
15+
@Stable
16+
class ImageViewerState internal constructor(
17+
scale: Float = 1f,
18+
offset: Offset = Offset.Zero,
19+
val minScale: Float = 1f,
20+
val maxScale: Float = 5f,
21+
) {
22+
var scale by mutableStateOf(scale)
23+
var offset by mutableStateOf(offset)
24+
25+
fun reset() {
26+
scale = 1f
27+
offset = Offset.Zero
28+
}
29+
}
30+
31+
/**
32+
* Call this from your Composable to keep the state across recompositions.
33+
*/
34+
@Composable
35+
fun rememberImageViewerState(
36+
initialScale: Float = 1f,
37+
initialOffset: Offset = Offset.Zero,
38+
minScale: Float = 1f,
39+
maxScale: Float = 5f,
40+
): ImageViewerState = remember {
41+
ImageViewerState(
42+
scale = initialScale,
43+
offset = initialOffset,
44+
minScale = minScale,
45+
maxScale = maxScale,
46+
)
47+
}
48+
49+
/**
50+
* A zoomable / pannable image-viewer composable.
51+
*
52+
* @param bitmap The photo to display.
53+
* @param state State returned from [rememberImageViewerState].
54+
* @param modifier Extra modifiers (size, background, etc.).
55+
*/
56+
@Composable
57+
fun ImageViewer(
58+
bitmap: ImageBitmap,
59+
state: ImageViewerState,
60+
modifier: Modifier = Modifier,
61+
) {
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+
}

app/src/main/kotlin/com/darkrockstudios/app/securecamera/viewphoto/ViewPhotoContent.kt

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.darkrockstudios.app.securecamera.viewphoto
22

3-
import androidx.compose.foundation.Image
43
import androidx.compose.foundation.background
54
import androidx.compose.foundation.layout.Box
65
import androidx.compose.foundation.layout.Column
@@ -16,7 +15,6 @@ import androidx.compose.ui.Modifier
1615
import androidx.compose.ui.draw.clipToBounds
1716
import androidx.compose.ui.graphics.ImageBitmap
1817
import androidx.compose.ui.graphics.asImageBitmap
19-
import androidx.compose.ui.layout.ContentScale
2018
import androidx.compose.ui.platform.LocalContext
2119
import androidx.compose.ui.res.stringResource
2220
import androidx.navigation.NavController
@@ -26,8 +24,6 @@ import com.darkrockstudios.app.securecamera.camera.PhotoDef
2624
import com.darkrockstudios.app.securecamera.camera.SecureImageManager
2725
import com.darkrockstudios.app.securecamera.preferences.AppPreferencesManager
2826
import com.darkrockstudios.app.securecamera.sharePhotoData
29-
import com.zhangke.imageviewer.ImageViewer
30-
import com.zhangke.imageviewer.rememberImageViewerState
3127
import kotlinx.coroutines.Dispatchers
3228
import kotlinx.coroutines.launch
3329
import kotlinx.coroutines.withContext
@@ -145,28 +141,25 @@ fun ViewPhotoContent(
145141
contentAlignment = Alignment.Center
146142
) {
147143
if (photo.photoFile.exists()) {
148-
val state = rememberImageViewerState {
149-
navController.navigateUp()
150-
}
151-
ImageViewer(
152-
state = state,
153-
modifier = Modifier
154-
.fillMaxSize()
155-
.clipToBounds()
156-
) {
157-
imageBitmap?.let {
158-
Image(
159-
bitmap = it,
160-
contentDescription = stringResource(id = R.string.photo_content_description),
161-
contentScale = ContentScale.Fit,
144+
val state = rememberImageViewerState(
145+
// onDismiss = {
146+
// navController.navigateUp()
147+
// }
148+
)
149+
imageBitmap?.let {
150+
ImageViewer(
151+
bitmap = it,
152+
state = state,
153+
modifier = Modifier
154+
.fillMaxSize()
155+
.clipToBounds()
156+
)
157+
} ?: run {
158+
Box(modifier = Modifier.fillMaxSize()) {
159+
Text(
160+
text = stringResource(R.string.photo_content_loading),
161+
modifier = Modifier.align(Alignment.Center)
162162
)
163-
} ?: run {
164-
Box(modifier = Modifier.fillMaxSize()) {
165-
Text(
166-
text = stringResource(R.string.photo_content_loading),
167-
modifier = Modifier.align(Alignment.Center)
168-
)
169-
}
170163
}
171164
}
172165
} else {

0 commit comments

Comments
 (0)