From cf8c5ef604cf18c8afdb8d7d427838e693cb6fcc Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Thu, 27 Aug 2026 18:46:31 +0300 Subject: [PATCH] =?UTF-8?q?ui(phone):=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BA?= =?UTF-8?q?=D0=BB=D1=8E=D1=87=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=81=D0=B5=D1=81?= =?UTF-8?q?=D1=81=D0=B8=D0=B9=20=D1=81=D1=82=D1=83=D0=BF=D0=B5=D0=BD=D1=87?= =?UTF-8?q?=D0=B0=D1=82=D1=8B=D0=BC=20=D1=81=D0=B2=D0=B0=D0=B9=D0=BF=D0=BE?= =?UTF-8?q?=D0=BC=20(=D0=BF=D0=BE=D1=80=D0=BE=D0=B3=20120dp,=20=C2=B11=20?= =?UTF-8?q?=D1=81=D0=B5=D1=81=D1=81=D0=B8=D1=8F=20=D0=B7=D0=B0=20=D0=B6?= =?UTF-8?q?=D0=B5=D1=81=D1=82)=20+=20=D0=BA=D1=80=D1=83=D0=BF=D0=BD=D0=B5?= =?UTF-8?q?=D0=B5=20=D1=82=D0=B5=D0=BA=D1=81=D1=82=20=D1=81=D0=BE=D0=BE?= =?UTF-8?q?=D0=B1=D1=89=D0=B5=D0=BD=D0=B8=D0=B9=20(14=E2=86=9220sp)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pw/binom/viewmate/phone/ui/ChatScreen.kt | 62 ++++++++++++++++++- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/app-phone/src/main/kotlin/pw/binom/viewmate/phone/ui/ChatScreen.kt b/app-phone/src/main/kotlin/pw/binom/viewmate/phone/ui/ChatScreen.kt index 9714d59..f906fe5 100644 --- a/app-phone/src/main/kotlin/pw/binom/viewmate/phone/ui/ChatScreen.kt +++ b/app-phone/src/main/kotlin/pw/binom/viewmate/phone/ui/ChatScreen.kt @@ -1,6 +1,7 @@ package pw.binom.viewmate.phone.ui import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.detectHorizontalDragGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -33,6 +34,8 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -48,7 +51,9 @@ import pw.binom.viewmate.phone.agent.Session /** * Экран «Чат с Порфирием»: история из SQLite, ввод текста, отправка в * локального ассистента (та же очередь, что и голос с очков). Управление - * сессиями: новая, переключение (выпадающий список), сброс текущей. + * сессиями: новая, переключение (выпадающий список ИЛИ ступенчатый горизонтальный + * свайп ленты сообщений — на одну сессию за жест, без инерции; TZ-ui-four п.3), + * сброс текущей. */ @Composable fun ChatScreen() { @@ -104,6 +109,25 @@ fun ChatScreen() { } } + // Ступенчатое переключение сессий свайпом (TZ-ui-four п.3): фиксированный + // порог смещения в px (плотность экрана). + val thresholdPx = with(LocalDensity.current) { CHAT_SESSION_SWIPE_THRESHOLD.toPx() } + + /** + * Ступенчатое переключение: ровно ОДИН шаг (+1 — следующая, −1 — предыдущая), + * без перескока сразу на несколько сессий, без инерции/флинга; + * за границей списка — ничего. + */ + fun switchSessionBy(delta: Int) { + if (sessions.size < 2) return + val idx = sessions.indexOfFirst { it.id == activeId }.let { if (it >= 0) it else 0 } + val target = idx + delta + if (target < 0 || target >= sessions.size) return + val s = sessions[target] + activeId = s.id + app.selectChatSession(s.id) + } + Column( modifier = Modifier .fillMaxSize() @@ -173,7 +197,31 @@ fun ChatScreen() { Spacer(Modifier.height(4.dp)) // Лента сообщений - Box(modifier = Modifier.weight(1f).fillMaxWidth()) { + Box( + modifier = Modifier + .weight(1f) + .fillMaxWidth() + // Ступенчатая смена сессии (TZ-ui-four п.3): горизонтальный свайп + // ленты за фиксированный порог = ±одна сессия (свайп влево — + // следующая, вправо — предыдущая), мгновенно, без физики/инерции. + // foundation 1.8: onHorizontalDrag даёт (change, суммарный сдвиг x с + // начала жеста) — берём суммарный сдвиг без накопления по кадрам. + .pointerInput(sessions.size) { + var draggedPx = 0f + detectHorizontalDragGestures( + onHorizontalDrag = { _, amount -> draggedPx = amount }, + onDragCancel = { draggedPx = 0f }, + onDragEnd = { + when { + draggedPx <= -thresholdPx -> switchSessionBy(1) + draggedPx >= thresholdPx -> switchSessionBy(-1) + else -> Unit + } + draggedPx = 0f + }, + ) + }, + ) { if (messages.isEmpty()) { Text( text = "Спроси что-нибудь — например, «сколько сейчас время?»", @@ -228,7 +276,9 @@ private fun MessageBubble(role: ChatRole, text: String) { Text( text = text, color = PhoneText, - fontSize = 14.sp, + // Базовый шрифт сообщений (TZ-ui-four п.2): крупнее, чтобы читалось + // без напряжения (14.sp → 20.sp, ~+43%); вёртка баллонов не менялась. + fontSize = 20.sp, modifier = Modifier .padding(horizontal = 10.dp, vertical = 6.dp) .background( @@ -239,3 +289,9 @@ private fun MessageBubble(role: ChatRole, text: String) { ) } } + +/** + * Фиксированный порог ступенчатого свайпа переключения сессий (TZ-ui-four п.3): + * жест «перетолкал» ленту на него — смена ровно на одну сессию; меньше — назад. + */ +private val CHAT_SESSION_SWIPE_THRESHOLD = 120.dp