Files
view-mate/TASK.md
T

6.7 KiB
Raw Permalink Blame History

Задача: масштаб на всех экранах очков + сохранение скролла каталога

Баг 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:

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<Path, Pair<Int, Int>>() — сохранённые позиции по пути
  • в openSeries/openSeason/openFolder: сначала savedScroll[path] = scrollIndex to scrollOffset, потом менять path
  • в back(): после смены path — восстановить (scrollIndex, scrollOffset) = savedScroll[path] ?: (0,0)
  • методы setScroll(index, offset) (для snapshotFlow из UI) и getSavedScroll(): Pair<Int,Int> (текущий path)

В CatalogScreen composable:

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. Ручная проверка: скролл каталога вниз → зайти в папку → назад → позиция на месте; то же для вложенных папок.