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:
iu
2026-10-13 12:30:49 +03:00
parent 43f5520236
commit f6df9e23f2
14 changed files with 949 additions and 10 deletions
@@ -0,0 +1,39 @@
# Proposal
## Why
В веб-просмотрщике фото можно приблизить (колесо, `Space`), а в Android-клиенте
изображения в полноэкранном просмотре открываются только во вписанном виде — в 360-панораме
зум есть, в обычных фото нет. Пользователь не может рассмотреть деталь снимка на телефоне.
## What Changes
- В полноэкранный просмотр обычных изображений добавляется зум пальцами:
- pinch увеличивает/уменьшает, панорамирование при увеличении, двойной тап
переключает вписанный вид и 100% натурального размера с якорем в точку тапа.
- Предел зума — 200% натурального размера изображения, минимум — вписанный вид
(семантика веб-просмотрщика, адаптированная под тач).
- При входе в зум догружается оригинал (`originalUrl`), другие фото сбрасываются во
вписанный вид при переходе.
- Пока изображение увеличено, горизонтальный свайп не листает альбом — жест отдан
панорамированию (как для панорамы).
- Исключения: видео и 360-панорама не затрагиваются (у панорамы зум уже есть).
## Capabilities
### New Capabilities
<!-- none -->
### Modified Capabilities
- `android-client`: требование «Просмотр медиа на весь экран» расширяется зумом
изображений — pinch, панорамирование, двойной тап, предел, догрузка оригинала,
поведение pager и сброс при смене фото.
## Impact
- `android/app/src/main/java/com/galo/android/ui/viewer/ViewerScreen.kt` — обёртка
изображения и проброс состояния зума в `ViewerContent` (блокировка pager).
- Новый zoomable-компонент изображения (ручной `pointerInput` + `graphicsLayer`),
без новых зависимостей.
- Тесты: Robolectric «свайп не листает увеличенное фото»; юнит на предел и якорь.
- Серверная часть и веб-клиент не меняются.