feat(viewer): add pinch-to-zoom for photos on Android
Add pinch/pan/double-tap zoom to the full-screen photo viewer, mirroring the web viewer semantics: - scale is a multiplier of the natural size, capped at 200%; the fitted view is the lower bound - double tap toggles fitted view <-> 100% natural anchored at the tap - the original loads on zoom, with the preview kept as a placeholder so entering zoom never flashes a black surface - the pager is disabled while zoomed (drags pan the image) and the zoom resets when leaving a photo - video and 360 panoramas are unaffected Implement PhotoZoom (zoom math) and ZoomableImage (gestures), wire it into ViewerContent/MediaPage, and cover it with unit, Compose and device tests. OpenSpec change: add-mobile-photo-zoom
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
package com.galo.android
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.test.doubleClick
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.compose.ui.test.performTouchInput
|
||||
import androidx.compose.ui.test.pinch
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.galo.android.ui.theme.GaloTheme
|
||||
import com.galo.android.ui.viewer.TAG_ZOOMABLE_IMAGE
|
||||
import com.galo.android.ui.viewer.ZoomableImage
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
/**
|
||||
* Device check for the photo zoom: a real pinch enlarges the image and a double
|
||||
* tap returns it to the fitted view (the 200% cap and pan clamp are covered by
|
||||
* the unit tests for `PhotoZoom`).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ZoomGestureTest {
|
||||
|
||||
@get:Rule
|
||||
val compose = createComposeRule()
|
||||
|
||||
@Test
|
||||
fun pinchZoomsAndDoubleTapResets() {
|
||||
val zoomed = AtomicBoolean(false)
|
||||
compose.setContent {
|
||||
GaloTheme(dynamicColor = false) {
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
ZoomableImage(
|
||||
previewUrl = null,
|
||||
hiResUrl = null,
|
||||
contentDescription = null,
|
||||
naturalWidth = 2000,
|
||||
naturalHeight = 1000,
|
||||
onZoomChanged = { zoomed.set(it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compose.onNodeWithTag(TAG_ZOOMABLE_IMAGE).performTouchInput {
|
||||
pinch(
|
||||
start0 = center + Offset(-60f, 0f),
|
||||
end0 = center + Offset(-260f, 0f),
|
||||
start1 = center + Offset(60f, 0f),
|
||||
end1 = center + Offset(260f, 0f),
|
||||
)
|
||||
}
|
||||
compose.waitForIdle()
|
||||
assertTrue("pinch should zoom the image", zoomed.get())
|
||||
|
||||
compose.onNodeWithTag(TAG_ZOOMABLE_IMAGE).performTouchInput { doubleClick() }
|
||||
compose.waitForIdle()
|
||||
assertFalse("double tap should return to the fitted view", zoomed.get())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user