Make jump-to-latest the same chevron, pointing down
It was a labelled button beside a tool group that collapses with a drawn chevron -- two controls doing the same kind of thing in two visual languages. Now one `Chevron` composable serves both directions, parameterised rather than copied, since a pair that differs by a minus sign drifts and the drift is a bug in exactly one direction. The comment it replaces argued against an arrow here, on the grounds that the list is laid out upside down. That reasoning was about the code: nobody reading the screen knows the list is reversed, and on screen the newest message is at the bottom, which is where this goes. It draws no text, so the name lives in its content description -- the whole of what a screen reader has, and the answer to "what was that arrow for" later. Verified on the emulator: scrolled up, the chevron appears bottom centre matching the group's; tapped, it returns to the newest message and takes itself away. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
011ed0d0e1
commit
73923dbbc3
3 files changed
+78
-26
No files matched your search
@@ -0,0 +1,53 @@
|
||||
package com.example.aiapp
|
||||
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* A chevron, pointing up or down.
|
||||
*
|
||||
* Drawn rather than set in a font: a chevron from an icon font is one of the glyphs a system font
|
||||
* may simply not have, and the reader who gets an empty box instead is never the one who wrote it.
|
||||
*
|
||||
* One composable for both directions rather than two that differ by a minus sign -- the pair would
|
||||
* drift, and the drift would be a bug in exactly one direction.
|
||||
*
|
||||
* It draws no label of its own, so every caller owes it a `contentDescription`: this is the whole
|
||||
* of what assistive technology has to go on, and it is also the answer to "what was that arrow
|
||||
* for" six months from now.
|
||||
*/
|
||||
@Composable
|
||||
fun Chevron(
|
||||
pointingUp: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
colour: Color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
) {
|
||||
Canvas(modifier.width(20.dp).height(10.dp)) {
|
||||
val inset = 2.dp.toPx()
|
||||
val point = if (pointingUp) inset else size.height - inset
|
||||
val ends = if (pointingUp) size.height - inset else inset
|
||||
val stroke = 2.dp.toPx()
|
||||
drawLine(
|
||||
colour,
|
||||
Offset(inset, ends),
|
||||
Offset(size.width / 2, point),
|
||||
strokeWidth = stroke,
|
||||
cap = StrokeCap.Round,
|
||||
)
|
||||
drawLine(
|
||||
colour,
|
||||
Offset(size.width / 2, point),
|
||||
Offset(size.width - inset, ends),
|
||||
strokeWidth = stroke,
|
||||
cap = StrokeCap.Round,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,10 @@ import androidx.compose.material3.FilledTonalButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -660,16 +664,29 @@ fun SessionScreen(
|
||||
|
||||
// Only while the newest message is off-screen. Reading back
|
||||
// through a conversation is a place to be, not a state to be
|
||||
// rescued from, so this waits to be wanted -- and says where it
|
||||
// goes rather than drawing an arrow, since an arrow in a list
|
||||
// that is laid out upside down is the one thing nobody should
|
||||
// have to reason about.
|
||||
// rescued from, so this waits to be wanted.
|
||||
//
|
||||
// Down, and the same chevron a tool group collapses with: the
|
||||
// list is built upside down internally, but nobody reading it
|
||||
// knows that -- on screen the newest message is at the bottom,
|
||||
// which is where this goes. The name is carried in the
|
||||
// description, since an arrow alone says nothing to a screen
|
||||
// reader and nothing to whoever finds this in six months.
|
||||
if (!followTail) {
|
||||
FilledTonalButton(
|
||||
Surface(
|
||||
onClick = { scope.launch { listState.animateScrollToItem(0) } },
|
||||
modifier = Modifier.align(Alignment.BottomCenter).padding(bottom = 12.dp),
|
||||
shape = CircleShape,
|
||||
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
modifier =
|
||||
Modifier.align(Alignment.BottomCenter)
|
||||
.padding(bottom = 12.dp)
|
||||
.semantics { contentDescription = "Jump to latest" },
|
||||
) {
|
||||
Text("Jump to latest", style = MaterialTheme.typography.bodySmall)
|
||||
Chevron(
|
||||
pointingUp = false,
|
||||
colour = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,25 +132,7 @@ private fun CollapseBar(onToggle: () -> Unit) {
|
||||
.padding(vertical = 10.dp),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
) {
|
||||
// Drawn rather than set in a font: a chevron from an icon font is one of the glyphs a
|
||||
// system font may simply not have, and the reader who gets the empty box is never me.
|
||||
androidx.compose.foundation.Canvas(Modifier.width(20.dp).height(10.dp)) {
|
||||
val inset = 2.dp.toPx()
|
||||
drawLine(
|
||||
colour,
|
||||
Offset(inset, size.height - inset),
|
||||
Offset(size.width / 2, inset),
|
||||
strokeWidth = 2.dp.toPx(),
|
||||
cap = StrokeCap.Round,
|
||||
)
|
||||
drawLine(
|
||||
colour,
|
||||
Offset(size.width / 2, inset),
|
||||
Offset(size.width - inset, size.height - inset),
|
||||
strokeWidth = 2.dp.toPx(),
|
||||
cap = StrokeCap.Round,
|
||||
)
|
||||
}
|
||||
Chevron(pointingUp = true, colour = colour)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user