feat(app-phone): полноценный UI телефона — каталог Jellyfin, детали, пульт очков, реальное скачивание с S3

- 4 экрана по макету sketches/phone-ui: Каталог (табы Фильмы/Сериалы/Папки, поиск, бейджи зеркала), Детали (озвучки, статус конвертации, чипы скачивания), Очки (пульт, статус), Чат-заглушка
- S3 SigV4 presign (S3Presigner): mirror-client read-only креды; watch() и DownloadManager подписывают URL (бакет приватный)
- DownloadManager: реальное скачивание video+audio с прогрессом, readTimeout=0 (длинные файлы)
- PhoneActions.watch: параметр presign (app-host работает без изменений)
- TESTING.md: блок телефонных кейсов TP-1..6
This commit is contained in:
Hermes Agent
2026-08-19 09:13:56 +03:00
parent 4458678698
commit 6069400b88
19 changed files with 2145 additions and 68 deletions
@@ -49,6 +49,18 @@ class GlassesHub(
private val _gesturesReceived = MutableStateFlow(0)
val gesturesReceived: StateFlow<Int> = _gesturesReceived.asStateFlow()
private val _batteryPercent = MutableStateFlow<Int?>(null)
val batteryPercent: StateFlow<Int?> = _batteryPercent.asStateFlow()
private val _storageUsedGb = MutableStateFlow<Double?>(null)
val storageUsedGb: StateFlow<Double?> = _storageUsedGb.asStateFlow()
private val _storageTotalGb = MutableStateFlow<Double?>(null)
val storageTotalGb: StateFlow<Double?> = _storageTotalGb.asStateFlow()
private val _positionMs = MutableStateFlow<Long?>(null)
val positionMs: StateFlow<Long?> = _positionMs.asStateFlow()
fun add(session: DefaultWebSocketServerSession) {
sessions[session] = Unit
_connected.value = sessions.size
@@ -88,10 +100,15 @@ class GlassesHub(
}
is GlassesStatus -> {
_batteryPercent.value = msg.batteryPercent
_storageUsedGb.value = msg.storageUsedGb
_storageTotalGb.value = msg.storageTotalGb
log("glasses", "статус: батарея ${msg.batteryPercent}%, память ${msg.storageUsedGb}/${msg.storageTotalGb} ГБ")
}
is PlaybackPosition -> {
_positionMs.value = msg.positionMs
this.state.playing = msg.playing
val state = if (msg.playing) "играет" else "пауза"
log("glasses", "позиция: ${msg.positionMs} мс, $state")
}