Merge branch 'main' of git.arirex.me:iris/ai-app

This commit is contained in:
iris committed 2026-08-31 01:59:00 -04:00
commit 19072a7e96
4 files changed
+60 -29

No files matched your search

@@ -1,7 +1,6 @@
package com.example.aiapp
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
@@ -99,7 +98,10 @@ fun MainScreen(
// keep its own line. They sit on the title's row because they act on the whole
// screen -- everything below this row is one tab's business, and a control belongs
// with the thing it acts on.
Row(horizontalArrangement = Arrangement.spacedBy(GLYPH_BUTTON_GAP)) {
// Flush against each other: a glyph button carries its own padding, so two of them
// side by side already have two rings between their marks and one ring plus this
// row's padding to the screen edge.
Row {
GlyphButton(REFRESH_GLYPH, "Refresh", { refreshToken++ })
GlyphButton(SETTINGS_GLYPH, "Settings", onSettings)
}
@@ -107,21 +107,48 @@ val BELL_GLYPH = glyph(0xF009A)
val USAGE_GLYPH = glyph(0xF201)
/**
* The size of a glyph button's box, which is the size of the glyph itself.
* The size an icon draws at beside a line of text.
*
* Not the 48dp of a default `IconButton`: that box centres its drawing inside itself, so aligning
* the box against a title aligns nothing a reader can see, and the pressed-state ripple lands on
* the box rather than on the mark. A box that *is* the glyph aligns like any other content and
* takes its ripple with it. The cost is the touch target -- `minimumInteractiveComponentSize` is
* applied inside the caller's modifier, so a size set here wins over it.
* 17 rather than the 20 it was while the font was the proportional face. A glyph there filled at
* most 0.83 em of its point size and most filled a good deal less, so the number was standing in
* for the headroom above the tallest one; in the Mono face every glyph fills its em exactly, and
* keeping 20 would have made every icon in the app step up by a fifth for no reason anybody asked
* for. This is what the largest of them already drew at.
*/
private val GLYPH_BUTTON_SIZE = 28.dp
private val GLYPH_SIZE = 17.sp
/**
* Restores the separation the 48dp boxes used to provide, now that the boxes are the size of what
* they draw: two of these plus the gap come to the same 48dp centre-to-centre spacing.
* The same measurement in dp: a glyph's em box is its point size, and a layout is laid out in dp.
*/
val GLYPH_BUTTON_GAP = 48.dp - GLYPH_BUTTON_SIZE
private val GLYPH_EXTENT = GLYPH_SIZE.value.dp
/**
* The square a glyph button occupies: the mark, plus the same ring of padding on all four sides.
*
* The ring is the whole spacing rule. Every gap around a header icon comes out of it -- one ring to
* the screen edge, two where a button meets its neighbour -- so nothing outside has to add a gap of
* its own, and a mark cannot end up further from the button beside it than from the edge of the
* screen. That is what it was: the box was the size of the mark (28dp) and the separation was
* bolted on beside it, which left the two header icons 31dp apart and the outer one 14dp from the
* edge, so a pair that acts on one screen read as two unrelated marks with one falling off it.
*
* 40dp is Material's own icon-button state layer, and it is also what the pressed-state ripple
* draws: at 28dp that circle was inscribed in the mark's own corners. The touch target grows with
* it, from well under the platform's 48dp minimum to within 8dp of it.
*/
private val GLYPH_BUTTON_SIZE = 40.dp
/**
* The ring itself, for putting something that is *not* a glyph button next to one -- a title beside
* a back arrow.
*
* Two glyph buttons need nothing between them: each brings its own ring and the two add up, which
* is why a row of them sets no spacing. Text brings none, so the second ring has to be asked for.
* Without it the pressed-state circle, which fills the whole square, arrives at the first letter of
* the title -- and the gap a reader sees between the mark and that title is then half the one
* between the two marks at the other end of the same row.
*/
val GLYPH_BUTTON_MARGIN = (GLYPH_BUTTON_SIZE - GLYPH_EXTENT) / 2
/**
* A glyph you can press: the icon equivalent of a `TextButton`.
@@ -166,16 +193,17 @@ fun Glyph(
colour: Color = MaterialTheme.colorScheme.primary,
size: TextUnit = GLYPH_SIZE,
) {
Text(glyph, fontFamily = NerdIcons, fontSize = size, color = colour, modifier = modifier)
// Line height of the point size, which for this font is the square the glyph draws in: its
// ascent and descent add up to exactly one em, and every glyph in the Mono face fills that em.
// Left to the inherited body style the line box was 24sp tall around a 17sp-wide mark, so a
// glyph took a seventh more vertical space than horizontal wherever one is drawn without a box
// around it -- and where there is a box, that leading is what its padding is measured through.
Text(
glyph,
fontFamily = NerdIcons,
fontSize = size,
lineHeight = size,
color = colour,
modifier = modifier,
)
}
/**
* The size an icon draws at beside a line of text.
*
* 17 rather than the 20 it was while the font was the proportional face. A glyph there filled at
* most 0.83 em of its point size and most filled a good deal less, so the number was standing in
* for the headroom above the tallest one; in the Mono face every glyph fills its em exactly, and
* keeping 20 would have made every icon in the app step up by a fifth for no reason anybody asked
* for. This is what the largest of them already drew at.
*/
private val GLYPH_SIZE = 17.sp
@@ -5,7 +5,6 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
@@ -1283,10 +1282,12 @@ fun SessionScreen(settings: ServerSettings, summary: SessionSummary, onBack: ()
Column(Modifier.fillMaxSize()) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 4.dp),
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 4.dp),
) {
GlyphButton(BACK_GLYPH, "Back", onBack)
Spacer(Modifier.width(8.dp))
// A ring's worth, which is what the arrow already keeps on its other three sides --
// the pair of glyph buttons at the far end of this row get theirs from each other.
Spacer(Modifier.width(GLYPH_BUTTON_MARGIN))
Column(Modifier.weight(1f)) {
Text(title, style = MaterialTheme.typography.titleMedium)
// Machine first, then what runs on it -- the same order and the same wording
@@ -1318,7 +1319,7 @@ fun SessionScreen(settings: ServerSettings, summary: SessionSummary, onBack: ()
// yellow or red near a limit -- and the theme's plain control colour whenever there
// is no measurement, since blue is the low end of the scale here and would read as
// "checked, and fine" about a machine nobody could reach.
Row(horizontalArrangement = Arrangement.spacedBy(GLYPH_BUTTON_GAP)) {
Row {
GlyphButton(
USAGE_GLYPH,
"Usage",
@@ -87,7 +87,7 @@ fun SettingsScreen(
// a capability being withheld but a promise it could not keep.
if (onBack != null) {
GlyphButton(BACK_GLYPH, "Back", onBack)
Spacer(Modifier.width(8.dp))
Spacer(Modifier.width(GLYPH_BUTTON_MARGIN))
}
Text(
"Server",