# Задача: масштаб на всех экранах очков + сохранение скролла каталога ## Баг 1: ConnectionScreen игнорирует масштаб **Симптом (пользователь):** «в режиме НЕ просмотра видео (пока ничего не включено) очки игнорируют выставленный масштаб». Плюс «масштаб будто бы не сохраняется — сразу после старта масштаб 1». **Корень:** масштаб (`GlassesApp.instance.scale`, 0.5..1) применяется ТОЛЬКО в `GlassesMovieScreen.kt` (контейнер `width((screenWidthPx * scale).toInt().px)` + offset к центру). А `ConnectionScreen()` в `app-glasses/src/main/kotlin/pw/binom/viewmate/glasses/MainActivity.kt` (строка ~354) рисует контент на ПОЛНУЮ половину глаза (`Modifier.width(screenWidthPx.px)`) — scale не читает вообще. Поэтому и «не сохраняется»: сохранённый scale в prefs есть, но на не-видео экране он не виден. **Фикс:** в `ConnectionScreen()` применять scale так же, как в GlassesMovieScreen: ```kotlin fun ConnectionScreen() { val connection = GlassesApp.instance.hostConnection val connected by connection.connected.collectAsState() val status by connection.status.collectAsState() val messages by connection.messages.collectAsState() val scale by GlassesApp.instance.scale.collectAsState() // ← НОВОЕ Binocular(scale = false) { val screenWidthPx = BinocularScreenSize.current.width val screenHeightPx = BinocularScreenSize.current.height // масштаб через layout — как в GlassesMovieScreen val containerW = (screenWidthPx * scale).toInt() val containerH = (screenHeightPx * scale).toInt() val offsetX = (screenWidthPx * (1f - scale)).toInt() // сдвиг к носу (левый глаз) Box( modifier = Modifier .width(containerW.px) .height(containerH.px) .offset(x = offsetX.px) .background(Color.Black), ) { ... существующий контент ... } } } ``` Внутри контейнера всё остаётся: Column с текстами, StatusPanelCompose, оверлей распознавания — но всё это теперь в уменьшенном контейнере. Импорт `androidx.compose.foundation.layout.offset` и `width/height` уже есть. ⚠️ ВАЖНО: `Modifier.offset(x=...)` — смещение внутри родителя. Внутри `Binocular(scale=false)` контент рисуется на половину 640. При scale<1 контейнер уже (например 448 при 0.7) и сдвинут к центру (к носу): offsetX = 640*(1-0.7)=192. Для левого глаза сдвиг вправо (к центру 640), правый глаз — как в GlassesMovieScreen (offset 0, контейнер прижат к центру слева). Смотри `scaleOffsetX` в GlassesMovieScreen.kt:143 — скопировать ту же логику (side из BinocularContent, там есть `Side.LEFT/RIGHT` через CompositionLocal). ## Баг 2: скролл каталога теряется при навигации **Симптом (пользователь):** «кручу вниз до нужной папки, захожу, тыкаю назад (кнопку или аппаратную) и я оказываюсь в предыдущем окне в самом начале (скролл не сохранился). Надо сохранять скролл, даже если зашёл в папку, потом ещё в папку, потом ещё — и т.д.» **Корень:** `app-phone/src/main/kotlin/pw/binom/viewmate/phone/ui/CatalogScreen.kt`: `LazyVerticalGrid` (строка 220) БЕЗ `LazyGridState` — позиция не хранится. `CatalogState` живёт на корне (переживает переходы), но позицию скролла не помнит. **Фикс:** добавить в `CatalogState`: - `private var scrollIndex by mutableStateOf(0)` / `scrollOffset by mutableStateOf(0)` — текущая позиция - `private val savedScroll = mutableMapOf>()` — сохранённые позиции по пути - в `openSeries`/`openSeason`/`openFolder`: **сначала** `savedScroll[path] = scrollIndex to scrollOffset`, **потом** менять path - в `back()`: после смены path — восстановить `(scrollIndex, scrollOffset) = savedScroll[path] ?: (0,0)` - методы `setScroll(index, offset)` (для snapshotFlow из UI) и `getSavedScroll(): Pair` (текущий path) В `CatalogScreen` composable: ```kotlin val gridState = rememberLazyGridState() // восстанавливаем позицию при смене пути/загрузке LaunchedEffect(state.path, state.items) { val (idx, off) = state.savedScrollForCurrentPath() gridState.scrollToItem(idx, off) } // сохраняем позицию при скролле LaunchedEffect(gridState) { snapshotFlow { gridState.firstVisibleItemIndex to gridState.firstVisibleItemScrollOffset }.collect { (idx, off) -> state.setScroll(idx, off) } } ``` и `LazyVerticalGrid(state = gridState, ...)`. Ключ сохранения — РОДИТЕЛЬСКИЙ path (тот, откуда ушли). При back() path меняется на родителя — достаём сохранённое. Вложенные папки работают автоматически: каждый уровень сохраняет свою позицию. Импорты: `androidx.compose.foundation.lazy.grid.rememberLazyGridState`, `androidx.compose.runtime.snapshotFlow`. ## Критерии приёмки 1. Сборка: `./gradlew :app-phone:assembleDebug :app-glasses:assembleDebug` — успех. 2. Юнит-тесты: `./gradlew :lib-core:testDebugUnitTest :app-phone:testDebugUnitTest :app-glasses:testDebugUnitTest` — зелёные. 3. Ручная проверка на очках: выставить масштаб 0.7 → ConnectionScreen (без видео) тоже уменьшен (чёрные поля по краям, картинка/текст сдвинуты к носу). 4. Ручная проверка: скролл каталога вниз → зайти в папку → назад → позиция на месте; то же для вложенных папок.