feat(ui): order journal newest-first; remove duplicate Servers tab header

This commit is contained in:
heaven 2026-06-16 12:30:53 +03:00
parent 9b5a3dee1d
commit 40adf31985
2 changed files with 11 additions and 11 deletions

View file

@ -154,7 +154,7 @@ fun ConnectionScreen(
} }
} }
// ── Scrollable journal — the only thing that scrolls; autoscrolls down ── // ── Scrollable journal — the only thing that scrolls; newest on top ──
Column(Modifier.fillMaxWidth().weight(1f).padding(horizontal = 18.dp)) { Column(Modifier.fillMaxWidth().weight(1f).padding(horizontal = 18.dp)) {
SectionLabel("Журнал", Modifier.padding(start = 2.dp, bottom = 10.dp)) SectionLabel("Журнал", Modifier.padding(start = 2.dp, bottom = 10.dp))
JournalList( JournalList(
@ -398,15 +398,17 @@ private fun JournalList(
} }
return return
} }
// Newest first: most recent event on top, like a conventional log.
val ordered = events.asReversed()
val listState = rememberLazyListState() val listState = rememberLazyListState()
// Auto-scroll to the newest event as it lands. Keyed on the last event's id, // Snap to the top as a new event lands. Keyed on the last event's id, NOT
// NOT events.size — the ring buffer saturates at MAX_EVENTS, so size stops // events.size — the ring buffer saturates at MAX_EVENTS, so size stops changing
// changing while new events keep arriving. // while new events keep arriving.
LaunchedEffect(events.lastOrNull()?.id) { LaunchedEffect(events.lastOrNull()?.id) {
if (events.isNotEmpty()) listState.animateScrollToItem(events.lastIndex) if (ordered.isNotEmpty()) listState.animateScrollToItem(0)
} }
LazyColumn(modifier, state = listState) { LazyColumn(modifier, state = listState) {
itemsIndexed(events, key = { _, e -> e.id }) { i, e -> itemsIndexed(ordered, key = { _, e -> e.id }) { i, e ->
val color = when (e.tone) { val color = when (e.tone) {
VpnState.TunnelEvent.Tone.OK -> Vojo.green VpnState.TunnelEvent.Tone.OK -> Vojo.green
VpnState.TunnelEvent.Tone.WARN -> Vojo.amber VpnState.TunnelEvent.Tone.WARN -> Vojo.amber
@ -420,8 +422,8 @@ private fun JournalList(
color = color, color = color,
error = e.tone == VpnState.TunnelEvent.Tone.ERROR, error = e.tone == VpnState.TunnelEvent.Tone.ERROR,
drawTop = i > 0, drawTop = i > 0,
drawBottom = i < events.lastIndex, drawBottom = i < ordered.lastIndex,
pulse = pulseLast && i == events.lastIndex, pulse = pulseLast && i == 0,
) )
} }
} }

View file

@ -77,9 +77,7 @@ fun ServersScreen(
contentPadding = androidx.compose.foundation.layout.PaddingValues(top = 14.dp, bottom = 28.dp), contentPadding = androidx.compose.foundation.layout.PaddingValues(top = 14.dp, bottom = 28.dp),
verticalArrangement = Arrangement.spacedBy(10.dp), verticalArrangement = Arrangement.spacedBy(10.dp),
) { ) {
item { // No "Серверы" section header here — the tab bar already labels this pane.
SectionLabel("Серверы · ${configs.size}", Modifier.padding(start = 4.dp, bottom = 4.dp))
}
items(configs, key = { it.id }) { cfg -> items(configs, key = { it.id }) { cfg ->
ServerRow( ServerRow(
cfg = cfg, cfg = cfg,