diff --git a/build.gradle.kts b/build.gradle.kts index b9ef7d796..5e85fae41 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -61,18 +61,26 @@ abstract class GitLatestTagValueSource : ValueSource heading + - items.joinToString("") { "\n ${it.label}: ${it.value}" } + items.joinToString("") { + // With the detail, which the screen only sets apart + // rather than shortens. Where a build came from is + // half of what makes the stamp worth pasting. + "\n ${it.label}: ${it.value}${it.detail.orEmpty()}" + } }, ) scope.launch { snackbars.show(copied, SnackbarTone.Success) } @@ -564,6 +573,12 @@ private fun CrashCard(report: String, onCopy: () -> Unit, onClear: () -> Unit) { * * A fact that can be good or bad says which by its colour, so the page answers "is anything wrong" * before it is read at all. + * + * A row may end in a [InfoItem.detail], set smaller and in the muted colour. It is part of the same + * value and stays in the same line — a reader copying a version out by hand still gets all of it — + * but it is not what the row is looked up for. On the build rows that is where the stamp came from; + * the commit is what someone comparing two devices reads, and the repository or machine after it is + * two thirds of the characters and almost never the answer. */ @Composable private fun InfoRow(row: InfoItem) { @@ -580,7 +595,15 @@ private fun InfoRow(row: InfoItem) { ) Spacer(Modifier.height(2.dp)) Text( - text = row.value, + text = + buildAnnotatedString { + append(row.value) + row.detail?.let { detail -> + val muted = + SpanStyle(fontSize = 12.sp, color = colors.onSurfaceVariant) + withStyle(muted) { append(detail) } + } + }, style = if (row.monospace) VectorMono.copy(fontSize = 15.sp) else MaterialTheme.typography.bodyLarge, @@ -616,11 +639,30 @@ private enum class Health { private data class InfoItem( val label: String, val value: String, + /** The tail of the value that is context rather than identity; set apart, never dropped. */ + val detail: String? = null, val health: Health = Health.Neutral, /** True where the value is an identifier to be compared character by character. */ val monospace: Boolean = true, ) +/** + * A build row: the version number, the commit, and — set apart — where that build was made. + * + * The stamp leads with the commit and says where after it, so the split is the commit's own length + * and needs no second opinion about which half is which. What is left includes the separator, which + * is worth keeping visible: `-` is a repository that holds this exact commit and `+` is a machine + * holding changes that no repository does. + * + * A stamp that names no commit — "unknown", from a build made where git could not be asked — is not + * cut at all. It goes to the muted half whole, because none of it is an identifier. + */ +private fun buildRow(label: String, number: String, reported: String?): InfoItem { + val stamp = reported?.takeIf { it.isNotBlank() } ?: return InfoItem(label, number) + val commit = buildStamp(stamp).commit.orEmpty() + return InfoItem(label, "$number · $commit", detail = stamp.removePrefix(commit)) +} + /** A heading, so the page reads as three short lists rather than one long one. */ @Composable private fun SectionHeading(text: String) { @@ -649,24 +691,23 @@ private fun buildSections( return listOf( str(R.string.info_section_build) to listOf( - InfoItem( + // The exact build, not just its number. Two builds share a version code whenever + // they sit at the same depth on different branches, so the stamp names where the + // build came from as well as the commit: the repository for a CI build, the machine + // for a local one from a modified tree. That is what a bug report needs and what + // the number alone cannot give. + buildRow( str(R.string.info_framework_version), - buildString { - append(status.versionLabel ?: unknown) - // The exact build, not just its number. Two builds share a version code - // whenever they sit at the same depth on different branches, so this names - // where the build came from as well as the commit: the repository for a CI - // build, the machine for a local one from a modified tree. That is what a - // bug report needs and what the number alone cannot give. - status.commit?.takeIf { it.isNotBlank() }?.let { append(" · ").append(it) } - }, + status.versionLabel ?: unknown, + status.commit, ), // Named separately from the framework, because they are flashed separately and are // not always the same build. When these two disagree, that is the answer to a whole // class of "it behaves oddly" reports. - InfoItem( + buildRow( str(R.string.info_manager_version), - "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE}) · ${BuildConfig.VERSION_HASH}", + "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})", + BuildConfig.VERSION_HASH, ), // Named by which scale the number is on. The two share a field and nothing else: 93 // is a legacy Xposed API, 101 is a libxposed one, and calling both "Xposed API" is diff --git a/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/update/FrameworkUpdateScreen.kt b/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/update/FrameworkUpdateScreen.kt index 01aef5fa6..31bee01e7 100644 --- a/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/update/FrameworkUpdateScreen.kt +++ b/manager/src/main/kotlin/org/matrix/vector/manager/ui/screens/update/FrameworkUpdateScreen.kt @@ -22,6 +22,7 @@ import androidx.compose.material.icons.rounded.RadioButtonChecked import androidx.compose.material.icons.rounded.History import org.matrix.vector.manager.data.github.ZipVariant import org.matrix.vector.manager.data.github.CanaryArtifact +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.material3.SingleChoiceSegmentedButtonRow import androidx.compose.material3.SegmentedButtonDefaults @@ -545,6 +546,16 @@ private fun VariantPicker( } } +/** + * How much of a version row the status on its right may take. + * + * Wide enough for "Installed" and "Older" on one line in every language shipped, and narrow enough + * that what is left still holds a build's name and its date without wrapping on a phone. The + * divergence clause is longer than that and wraps inside this width, which is the point of fixing + * it: one row's wordier status must not move where the next row's name begins. + */ +private val STATUS_WIDTH = 96.dp + /** * Every build on this channel, so "no update available" is not a dead end. * @@ -591,7 +602,13 @@ private fun VersionsSheet( onSelect(release) onDismiss() }, - headlineContent = { Text(release.title) }, + // One line each, always. What names the build is the same shape in every + // row — "Vector v2.0 canary 3060", then its date and channel — and a row + // that wraps because of what is beside it reads as a different kind of + // entry when it is not. + headlineContent = { + Text(release.title, maxLines = 1, overflow = TextOverflow.Ellipsis) + }, supportingContent = { Text( listOfNotNull( @@ -603,7 +620,9 @@ private fun VersionsSheet( stringResource(R.string.update_channel_release) }, ) - .joinToString(" · ") + .joinToString(" · "), + maxLines = 1, + overflow = TextOverflow.Ellipsis, ) }, leadingContent = { @@ -625,6 +644,13 @@ private fun VersionsSheet( }, ) }, + // A column of its own width, kept even when this row has nothing to say. + // The status is one word on most rows and a whole clause on the divergent + // one; sized to its content it would take the room the build's name needs + // and wrap that row alone, and a slot that disappears when empty would + // start each row's name in a different place. The label wraps inside its + // column instead, where it costs nothing: three lines of it are still + // shorter than the two the name and its date already occupy. trailingContent = { val label = when { @@ -633,17 +659,23 @@ private fun VersionsSheet( older -> stringResource(R.string.update_older) else -> null } - if (label != null) { - Text( - label, - style = MaterialTheme.typography.labelSmall, - color = - when { - installed -> colors.primary - diverged -> colors.tertiary - else -> colors.onSurfaceVariant - }, - ) + Box( + modifier = Modifier.width(STATUS_WIDTH), + contentAlignment = Alignment.CenterEnd, + ) { + if (label != null) { + Text( + label, + style = MaterialTheme.typography.labelSmall, + textAlign = TextAlign.End, + color = + when { + installed -> colors.primary + diverged -> colors.tertiary + else -> colors.onSurfaceVariant + }, + ) + } } }, colors = sheetRowColors, diff --git a/services/manager-service/src/main/aidl/org/lsposed/lspd/ILSPManagerService.aidl b/services/manager-service/src/main/aidl/org/lsposed/lspd/ILSPManagerService.aidl index 9b7eb51c7..15ab06767 100644 --- a/services/manager-service/src/main/aidl/org/lsposed/lspd/ILSPManagerService.aidl +++ b/services/manager-service/src/main/aidl/org/lsposed/lspd/ILSPManagerService.aidl @@ -148,10 +148,17 @@ interface ILSPManagerService { void installFrameworkZip(String zipPath, IFrameworkInstallCallback callback) = 57; /** - * The commit this daemon was built from, short, or null when it was not recorded. + * Which build this daemon is, or null when it was not recorded. * * The version code is the commit count on origin/master, so a branch build and the official * build of the same count are indistinguishable by number alone. This is what tells them apart. + * + * Not a bare hash, despite the name: it is the build stamp, which names where the build came + * from as well as what commit it was made from — `93d66473-JingMatrix-Vector` from CI, + * `93d66473` from a clean local tree, `93d66473+thinkpad` from a modified one. The commit + * always leads, so a caller that wants it takes the head and not the whole string; `-` is + * followed by the repository that holds that commit, `+` by the machine holding changes that + * no repository does. */ String getFrameworkCommit() = 58;