ui(phone): переключение сессий ступенчатым свайпом (порог 120dp, ±1 сессия за жест) + крупнее текст сообщений (14→20sp)
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
package pw.binom.viewmate.phone.ui
|
package pw.binom.viewmate.phone.ui
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.gestures.detectHorizontalDragGestures
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -33,6 +34,8 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
@@ -48,7 +51,9 @@ import pw.binom.viewmate.phone.agent.Session
|
|||||||
/**
|
/**
|
||||||
* Экран «Чат с Порфирием»: история из SQLite, ввод текста, отправка в
|
* Экран «Чат с Порфирием»: история из SQLite, ввод текста, отправка в
|
||||||
* локального ассистента (та же очередь, что и голос с очков). Управление
|
* локального ассистента (та же очередь, что и голос с очков). Управление
|
||||||
* сессиями: новая, переключение (выпадающий список), сброс текущей.
|
* сессиями: новая, переключение (выпадающий список ИЛИ ступенчатый горизонтальный
|
||||||
|
* свайп ленты сообщений — на одну сессию за жест, без инерции; TZ-ui-four п.3),
|
||||||
|
* сброс текущей.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatScreen() {
|
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(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@@ -173,7 +197,31 @@ fun ChatScreen() {
|
|||||||
Spacer(Modifier.height(4.dp))
|
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()) {
|
if (messages.isEmpty()) {
|
||||||
Text(
|
Text(
|
||||||
text = "Спроси что-нибудь — например, «сколько сейчас время?»",
|
text = "Спроси что-нибудь — например, «сколько сейчас время?»",
|
||||||
@@ -228,7 +276,9 @@ private fun MessageBubble(role: ChatRole, text: String) {
|
|||||||
Text(
|
Text(
|
||||||
text = text,
|
text = text,
|
||||||
color = PhoneText,
|
color = PhoneText,
|
||||||
fontSize = 14.sp,
|
// Базовый шрифт сообщений (TZ-ui-four п.2): крупнее, чтобы читалось
|
||||||
|
// без напряжения (14.sp → 20.sp, ~+43%); вёртка баллонов не менялась.
|
||||||
|
fontSize = 20.sp,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 10.dp, vertical = 6.dp)
|
.padding(horizontal = 10.dp, vertical = 6.dp)
|
||||||
.background(
|
.background(
|
||||||
@@ -239,3 +289,9 @@ private fun MessageBubble(role: ChatRole, text: String) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Фиксированный порог ступенчатого свайпа переключения сессий (TZ-ui-four п.3):
|
||||||
|
* жест «перетолкал» ленту на него — смена ровно на одну сессию; меньше — назад.
|
||||||
|
*/
|
||||||
|
private val CHAT_SESSION_SWIPE_THRESHOLD = 120.dp
|
||||||
|
|||||||
Reference in New Issue
Block a user