Skip to content

Commit

Permalink
Update PlantDetailView.kt
Browse files Browse the repository at this point in the history
android#868 : made image in detail screen zoomable
  • Loading branch information
Dhruv-Limbachiya committed Jul 30, 2024
1 parent 705e962 commit acf3f49
Showing 1 changed file with 164 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ import androidx.compose.foundation.Image
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.gestures.detectTransformGestures
import androidx.compose.foundation.gestures.draggable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
Expand Down Expand Up @@ -58,13 +61,20 @@ import androidx.compose.runtime.Composable

import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.scale
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInWindow
Expand All @@ -77,6 +87,7 @@ import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidViewBinding
import androidx.constraintlayout.compose.ConstraintLayout
Expand All @@ -97,6 +108,7 @@ import com.google.samples.apps.sunflower.data.Plant
import com.google.samples.apps.sunflower.databinding.ItemPlantDescriptionBinding
import com.google.samples.apps.sunflower.ui.SunflowerTheme
import com.google.samples.apps.sunflower.viewmodels.PlantDetailViewModel
import kotlin.math.max

/**
* As these callbacks are passed in through multiple Composables, to avoid having to name
Expand Down Expand Up @@ -267,6 +279,100 @@ private fun PlantDetailsContent(
}
}

//@OptIn(ExperimentalGlideComposeApi::class)
//@Composable
//private fun PlantImage(
// imageUrl: String,
// imageHeight: Dp,
// modifier: Modifier = Modifier,
// placeholderColor: Color = MaterialTheme.colorScheme.onSurface.copy(0.2f)
//) {
// var isLoading by remember { mutableStateOf(true) }
// // Define mutable state variables to keep track of the scale and offset.
// var scale by remember { androidx.compose.runtime.mutableFloatStateOf(1f) }
// var offset by remember { mutableStateOf(Offset(0f, 0f)) }
// Box(
// modifier
// .fillMaxWidth()
// .height(imageHeight)
// ) {
// if (isLoading) {
// // TODO: Update this implementation once Glide releases a version
// // that contains this feature: https://github.com/bumptech/glide/pull/4934
// Box(
// Modifier
// .fillMaxSize()
// .background(placeholderColor)
// )
// }
// GlideImage(
// model = imageUrl,
// contentDescription = null,
// modifier = Modifier
// .fillMaxWidth()
// .height(imageHeight)
// .pointerInput(Unit) {
// detectTransformGestures { _, pan, zoom, _ ->
// val newScale = (scale * zoom).coerceIn(1f, 3f)
//
// // Calculate new offset
// val newOffset = if (newScale == 1f) Offset(0f, 0f) else offset + pan
//
// // Get the size of the box
// val boxSize = this.size
//
// // Calculate the size of the image based on the new scale
// val imageWidth = boxSize.width * newScale
// val imageHeight = boxSize.height * newScale
//
// // Ensure the image does not move out of the box boundaries
// val maxX = (imageWidth - boxSize.width) / 2
// val maxY = (imageHeight - boxSize.height) / 2
// val clampedOffset = Offset(
// newOffset.x.coerceIn(-maxX, maxX),
// newOffset.y.coerceIn(-maxY, maxY)
// )
//
// scale = newScale
// offset = clampedOffset
// }
// }
// .graphicsLayer(
// scaleX = scale,
// scaleY = scale,
// translationX = offset.x,
// translationY = offset.y
// ),
// contentScale = ContentScale.Crop,
// ) {
// it.addListener(object : RequestListener<Drawable> {
// override fun onLoadFailed(
// e: GlideException?,
// model: Any?,
// target: Target<Drawable>,
// isFirstResource: Boolean
// ): Boolean {
// isLoading = false
// return false
// }
//
// override fun onResourceReady(
// resource: Drawable,
// model: Any,
// target: Target<Drawable>?,
// dataSource: DataSource,
// isFirstResource: Boolean
// ): Boolean {
// isLoading = false
// return false
// }
// })
// }
// }
//}



@OptIn(ExperimentalGlideComposeApi::class)
@Composable
private fun PlantImage(
Expand All @@ -276,14 +382,58 @@ private fun PlantImage(
placeholderColor: Color = MaterialTheme.colorScheme.onSurface.copy(0.2f)
) {
var isLoading by remember { mutableStateOf(true) }
var scale by remember { mutableFloatStateOf(1f) }
var imageSize by remember { mutableStateOf(IntSize.Zero) }
var offset by remember { mutableStateOf(Offset.Zero) }

Box(
modifier
.clip(RectangleShape) // Clip the box content
.fillMaxWidth()
.height(imageHeight)
.pointerInput(Unit) {
detectTransformGestures { _, pan, zoom, _ ->
// Calculate the new scale value, ensuring it stays within the allowed range
scale = (scale * zoom).coerceIn(1f, 3f)

// Calculate the new offset by adding the pan gesture delta to the current offset
val newOffset = offset + pan


// Calculate max movement within the box
val maxX = if (imageSize.width * scale > size.width) {
(imageSize.width * scale - size.width) / 2
} else {
0f
}

val maxY = if (imageSize.height * scale > size.height) {
(imageSize.height * scale - size.height) / 2
} else {
0f
}

// Update the offset, clamping it to the calculated bounds
offset = Offset(
x = newOffset.x.coerceIn(-maxX, maxX),
y = newOffset.y.coerceIn(-maxY, maxY)
)
}
}
.pointerInput(Unit) {
detectTapGestures(
onDoubleTap = {
if (scale > 1f) {
scale = 1f
offset = Offset.Zero
} else {
scale = 2f
}
}
)
}
) {
if (isLoading) {
// TODO: Update this implementation once Glide releases a version
// that contains this feature: https://github.com/bumptech/glide/pull/4934
Box(
Modifier
.fillMaxSize()
Expand All @@ -294,7 +444,17 @@ private fun PlantImage(
model = imageUrl,
contentDescription = null,
modifier = Modifier
.fillMaxSize(),
.fillMaxSize()
.graphicsLayer(
// Set the horizontal scaling factor, limiting zoom between 50% and 300%
scaleX = maxOf(0.5f, minOf(3f, scale)),
// Set the vertical scaling factor, limiting zoom between 50% and 300%
scaleY = maxOf(0.5f, minOf(3f, scale)),
//Set the horizontal translation (panning) based on the calculated offset
translationX = offset.x,
// Set the vertical translation (panning) based on the calculated offset
translationY = offset.y
),
contentScale = ContentScale.Crop,
) {
it.addListener(object : RequestListener<Drawable> {
Expand All @@ -316,6 +476,7 @@ private fun PlantImage(
isFirstResource: Boolean
): Boolean {
isLoading = false
imageSize = IntSize(resource.intrinsicWidth, resource.intrinsicHeight)
return false
}
})
Expand Down

0 comments on commit acf3f49

Please sign in to comment.