Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 32 additions & 8 deletions daemon/src/main/res/drawable/ic_statue_monochrome.xml

Large diffs are not rendered by default.

15 changes: 6 additions & 9 deletions manager-ui/src/main/kotlin/org/matrix/vector/ui/ModuleRow.kt
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,6 @@ package org.matrix.vector.ui

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
Expand DownExpand Up@@ -182,16 +181,14 @@ fun ModuleRow(
// The title's band. Both halves are fixed and both scroll, so however long a name or
// version string becomes neither can reach the other.
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
ScrollingLabel(
text = name,
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.SemiBold,
style =
MaterialTheme.typography.titleMedium.copy(
fontWeight = FontWeight.SemiBold
),
color = nameColor,
maxLines = 1,
overflow = TextOverflow.Clip,
modifier =
Modifier.weight(1f)
.basicMarquee(iterations = 1, repeatDelayMillis = 3_000),
modifier = Modifier.weight(1f),
)
if (versionName.isNotBlank()) {
Spacer(Modifier.width(10.dp))
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
packageorg.matrix.vector.ui

importandroidx.compose.foundation.basicMarquee
importandroidx.compose.material3.LocalTextStyle
importandroidx.compose.material3.Text
importandroidx.compose.runtime.Composable
importandroidx.compose.ui.Modifier
importandroidx.compose.ui.graphics.Color
importandroidx.compose.ui.text.TextStyle

/**
* How long the text waits before it moves, and how many times it goes.
*
* One pass, after a pause long enough to read the beginning of the line first. A label that keeps
* moving is a distraction on a screen someone is working on, and a label that starts moving the
* instant it appears is read from the middle. It says its piece once and settles.
*
* These live here and nowhere else. Two labels that scroll at different speeds on the same screen
* look like two kinds of thing, and nobody choosing a delay at a call site is thinking about the
* other four.
*/
privateconstvalPASSES=1
privateconstvalPAUSE_MS=2_000

/**
* One line of text that scrolls itself rather than being cut short or taking a neighbour's width.
*
* The names this app draws are written by other people — a module's title, a package, a version
* name a repository hands back like `1.58.245-ai-ui-label+B520-20260828T1203Z-full` — and any of
* them can be longer than the row it lands in. Wrapping is not an option in a fixed row: the text
* eats its neighbour, and a date beside it ends up a column one character wide. Truncating is
* usually worse than it looks, because the tail is so often the part that tells two builds apart.
*
* So the line is one line inside whatever width the caller gives it — usually
* `Modifier.weight(1f, fill = false)`, which serves the row's fixed parts first and leaves this the
* remainder — and the part that does not fit scrolls past once.
*
* The whole of it is here so that it is one behaviour. Everything that scrolls a name in either
* manager comes through this: the modules list, the scope screen's title, a version with an update
* mark on it, and the three places the store prints a release's name.
*/
@Composable
funScrollingLabel(
text:String,
modifier:Modifier = Modifier,
style:TextStyle = LocalTextStyle.current,
color:Color = Color.Unspecified,
) {
Text(
text = text,
style = style,
color = color,
maxLines =1,
softWrap =false,
modifier = modifier.basicMarquee(iterations =PASSES, repeatDelayMillis =PAUSE_MS),
)
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,7 +5,6 @@ import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
Expand DownExpand Up@@ -50,7 +49,7 @@ fun UpdatableVersion(
text: String,
hasUpdate: Boolean,
modifier: Modifier = Modifier,
/** Whether an over-long version scrolls past instead of being cut. */
/** Whether an over-long version scrolls past, as [ScrollingLabel], instead of being cut. */
marquee: Boolean = false,
style: TextStyle = Mono,
color: Color = LocalContentColor.current,
Expand DownExpand Up@@ -81,15 +80,17 @@ fun UpdatableVersion(
)
Spacer(Modifier.width(5.dp))
}
Text(
text = text,
style = style,
color = if (hasUpdate) markColor else color,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier =
if (marquee) Modifier.basicMarquee(iterations = 1, repeatDelayMillis = 2_000)
else Modifier,
)
val ink = if (hasUpdate) markColor else color
// Ellipsised only where it cannot scroll: a marquee draws its own text past the edge, and
// an ellipsis on top of that is a full stop in the middle of a moving line.
if (marquee) ScrollingLabel(text = text, style = style, color = ink)
else
Text(
text = text,
style = style,
color = ink,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,7 @@ package org.matrix.vector.ui.store

import org.matrix.vector.ui.R as UiR
import org.matrix.vector.ui.ToggleRow
import org.matrix.vector.ui.ScrollingLabel
import org.matrix.vector.ui.SheetHeading
import org.matrix.vector.ui.sheetRowColors
import org.matrix.vector.ui.theme.Mono
Expand DownExpand Up@@ -462,17 +463,22 @@ private fun InstallBar(
modifier = Modifier.size(18.dp),
)
Spacer(Modifier.width(8.dp))
Text(
when {
state.upgradable ->
stringResource(
if (state.sameVersion) UiR.string.store_badge_reinstall
else UiR.string.store_badge_update,
state.latest?.versionName.orEmpty(),
)
state.installed != null -> stringResource(UiR.string.store_reinstall)
else -> stringResource(UiR.string.store_install)
}
// One line, whatever the version name is: a button that grows a second row
// to fit a build identifier moves the bar under the reader's thumb.
ScrollingLabel(
text =
when {
state.upgradable ->
stringResource(
if (state.sameVersion) UiR.string.store_badge_reinstall
else UiR.string.store_badge_update,
state.latest?.versionName.orEmpty(),
)
state.installed != null ->
stringResource(UiR.string.store_reinstall)
else -> stringResource(UiR.string.store_install)
},
modifier = Modifier.weight(1f, fill = false),
)
}
}
Expand DownExpand Up@@ -658,27 +664,39 @@ private fun ReleaseCard(
)
.padding(vertical = 4.dp),
) {
// The tag, not the name: it carries the version code, which is what actually decides
// whether the platform will accept this over what is installed.
release.tagName?.let {
Text(text = it, style = Mono, color = colors.onSurfaceVariant, maxLines = 1)
}
release.publishedAt.asRepositoryDate(locale)?.let {
if (release.tagName != null) {
// The tag and the date share what is left after the chevron, and the date is served
// first: it is a fixed handful of characters, while a tag is whatever the publisher
// wrote and is regularly longer than the screen. Measured the other way round the tag
// would push the date into a column one character wide.
Row(modifier = Modifier.weight(1f), verticalAlignment = Alignment.CenterVertically) {
// The tag, not the name: it carries the version code, which is what
// actually decides whether the platform will accept this over what is
// installed.
release.tagName?.let {
ScrollingLabel(
text = it,
style = Mono,
color = colors.onSurfaceVariant,
modifier = Modifier.weight(1f, fill = false),
)
}
release.publishedAt.asRepositoryDate(locale)?.let {
if (release.tagName != null) {
Text(
text = " · ",
style = MaterialTheme.typography.labelSmall,
color = colors.outlineVariant,
)
}
Text(
text = " · ",
style = MaterialTheme.typography.labelSmall,
color = colors.outlineVariant,
text = it,
style = MaterialTheme.typography.labelMedium,
color = colors.onSurfaceVariant,
maxLines = 1,
)
}
Text(
text = it,
style = MaterialTheme.typography.labelMedium,
color = colors.onSurfaceVariant,
)
}
if (hasNotes) {
Spacer(Modifier.weight(1f))
Icon(
Icons.Rounded.ExpandMore,
contentDescription = disclose,
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,7 @@ import androidx.compose.material3.ModalBottomSheet
importandroidx.compose.material3.SheetValue
importandroidx.compose.material3.rememberBottomSheetState
importorg.matrix.vector.ui.ChoiceRow
importorg.matrix.vector.ui.ScrollingLabel
importorg.matrix.vector.ui.SheetHeading
importandroidx.compose.foundation.clickable
importandroidx.compose.foundation.layout.Arrangement
Expand DownExpand Up@@ -401,6 +402,11 @@ private fun StoreRow(entry: StoreEntry, onClick: () -> Unit) {
entry.latest?.versionName.orEmpty(),
),
tint = colors.primary,
// The version name in this badge is the publisher's, and can be
// longer than the row. It takes what is left after the date rather
// than the other way round: the date is a fixed handful of
// characters and the badge is not.
modifier =Modifier.weight(1f, fill =false),
)
entry.installed !=null->
RowBadge(
Expand All@@ -422,11 +428,11 @@ private fun StoreRow(entry: StoreEntry, onClick: () -> Unit) {
}

@Composable
privatefunRowBadge(icon:ImageVector, text:String, tint:Color) {
Row(verticalAlignment =Alignment.CenterVertically) {
privatefunRowBadge(icon:ImageVector, text:String, tint:Color, modifier:Modifier = Modifier) {
Row(modifier = modifier, verticalAlignment =Alignment.CenterVertically) {
Icon(icon, contentDescription =null, modifier =Modifier.size(14.dp), tint = tint)
Spacer(Modifier.width(4.dp))
Text(text = text, style =MaterialTheme.typography.labelMedium, color = tint)
ScrollingLabel(text = text, style =MaterialTheme.typography.labelMedium, color = tint)
}
}

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,7 +32,7 @@ import org.matrix.vector.ui.sheetRowColors
/**
* Where a version stands relative to the running build — the one distinction each row is marked by.
*
* [Installed] is the build actually running (a filled dot, the accent). [Diverged] carries the same
* [Installed] is the build actually running (its dot wears the accent). [Diverged] carries the same
* version number but was not made from this release — another branch, or a working tree with changes
* — so it looks installed by the number alone and must be told apart. [Older] sits below the running
* build. [None] is any other version (a newer one on offer, or a sibling channel's build).
Expand DownExpand Up@@ -68,8 +68,10 @@ private val STATUS_WIDTH = 96.dp
* name, date, channel and status, tapping one to switch the screen to it.
*
* A scrollable list rather than a dropdown, so an older build carries the same weight as the newest
* and nothing is hidden past an edge. The rows are marked the way an activity feed marks the commit
* you are on — a filled dot against hollow ones — so the running build is findable at a glance.
* and nothing is hidden past an edge. The dots are a radio group and behave like one: the filled
* one is the build the screen is showing, and it moves to whichever row is tapped. What is
* *installed* is a separate fact and is told separately — the dot's colour and the status word
* beside it — because the two rows are only the same one until the reader picks something else.
*
* Localised through [LocalDialogLocalizer] like every shared sheet: a sheet is its own window and
* drops the host's in-app language override on the way in, so it is re-applied inside.
Expand DownExpand Up@@ -99,9 +101,15 @@ fun VersionHistorySheet(
supportingContent = {
Text(item.subtitle, maxLines = 1, overflow = TextOverflow.Ellipsis)
},
// The filled dot is the row the screen is showing, because that is what a
// list of dots means everywhere else: tapping one moves it. The installed
// build is still marked, by the colour of its dot and by the word in the
// status column — reading it off the fill instead would leave the reader
// who has just tapped an older build with no dot against the row they are
// looking at, and a filled one against a row they are not.
leadingContent = {
Icon(
if (item.status == VersionStatus.Installed) Icons.Rounded.RadioButtonChecked
if (item.selected) Icons.Rounded.RadioButtonChecked
else Icons.Rounded.RadioButtonUnchecked,
contentDescription = null,
tint =
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -73,7 +73,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.foundation.background
import androidx.compose.foundation.basicMarquee
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -41,7 +41,6 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.border
import org.matrix.vector.ui.contextClickable
import androidx.compose.foundation.shape.CircleShape
Expand DownExpand Up@@ -112,6 +111,7 @@ import org.matrix.vector.ui.show
import org.matrix.vector.manager.ui.components.PackageActionResult
import org.matrix.vector.manager.ui.components.PackageActionSheet
import org.matrix.vector.ui.SearchField
import org.matrix.vector.ui.ScrollingLabel
import org.matrix.vector.ui.theme.Mono

class ScopeViewModelFactory(private val packageName: String, private val userId: Int) :
Expand DownExpand Up@@ -262,20 +262,15 @@ fun ScopeScreen(
TopAppBar(
title = {
Column {
Text(
// The column is a fixed slice of one row, and module names are not.
// Rather than truncate the end of a name — often exactly the part that
// distinguishes two builds of the same module — it scrolls itself.
ScrollingLabel(
text = state.moduleName,
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.SemiBold,
maxLines = 1,
softWrap = false,
// The column is a fixed slice of one row, and module names are not.
// Rather than truncate the end of a name — often exactly the part that
// distinguishes two builds of the same module — it scrolls itself.
//
// Finite, not endless: this is a screen someone sits on while working
// through a long list, and a title that never stops moving is a
// distraction. It says its piece and settles.
modifier = Modifier.basicMarquee(iterations = 3),
style =
MaterialTheme.typography.titleMedium.copy(
fontWeight = FontWeight.SemiBold
),
)
Text(
text = packageName,
Expand Down
Loading