Second screen: fit the cover, scroll the panel, mark hardcore vs casual

The cover was CENTER_CROP in a grid cell. Box art is portrait and a cell is
not, so filling the cell ate the top and bottom of the cover -- the parts
carrying the logo and the title. It fits inside the tile now and letterboxes
against the tile background.

The panel root could not scroll, which was survivable while the tile list was
short and stopped being so the moment four more tiles were added to it. The
panel has no say in its own height -- the user chooses how many tiles and how
tall each one is -- so anything that did not fit was simply clipped off the
bottom of the display with nothing to say it was there.

"12/40" did not say whether those were earned in hardcore or casual, which is
the distinction RetroAchievements cares most about. The counts now carry
marks for both. rc_client reports a per-achievement mask where a hardcore
unlock also sets the softcore bit, so the casual figure is the softcore-ONLY
count rather than the total -- otherwise every hardcore unlock would be
counted twice.
This commit is contained in:
jpolo1224
2026-08-24 13:02:42 -04:00
parent 93c6aa25c7
commit f0d2a42bb2
@@ -512,7 +512,21 @@ object SecondScreen {
}
rootView.addView(grid, lp())
setContentView(rootView)
// Scrollable, because the panel has no say in how tall it gets: the user picks how
// many tiles are on it and how tall each one is, and a fixed root simply clipped
// whatever did not fit off the bottom of the display with no indication it was there.
setContentView(
android.widget.ScrollView(context).apply {
isFillViewport = true
addView(
rootView,
android.widget.FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
),
)
},
)
updateStats()
}
@@ -564,7 +578,11 @@ object SecondScreen {
private fun buildCoverTile(): View {
val image = android.widget.ImageView(context).apply {
scaleType = android.widget.ImageView.ScaleType.CENTER_CROP
// FIT_CENTER, not CENTER_CROP. Box art is portrait and a grid cell is not, so
// cropping to fill ate the top and bottom of the cover -- the parts with the
// logo and the title on them. Fitting shows the whole cover and letterboxes it
// against the tile background instead.
scaleType = android.widget.ImageView.ScaleType.FIT_CENTER
adjustViewBounds = true
}
// A label UNDER the image rather than instead of it. With no game, or before the
@@ -972,14 +990,27 @@ object SecondScreen {
private fun raPoints(): String {
if (raItems.isEmpty()) return I18n.get("secondScreen.tile.raPoints") + "\n"
val earned = raItems.filter { it.unlocked }.sumOf { it.points }
return "RA\n$earned/${raItems.sumOf { it.points }}"
return "🏆 RA\n$earned/${raItems.sumOf { it.points }}"
}
/**
* Unlocks, split by the mode they were earned in.
*
* "12/40" alone does not say whether those were earned in hardcore or casual, which is
* the distinction RetroAchievements cares most about -- so the counts carry the same
* marks RA uses. rc_client reports a per-achievement mask: bit 1 softcore, bit 2
* hardcore, and a hardcore unlock sets both, so the casual figure is deliberately the
* softcore-ONLY count rather than the total.
*/
private fun achievementSummary(): String {
if (raItems.isEmpty()) return I18n.get("secondScreen.tile.achievements") + "\n"
val unlocked = raItems.filter { it.unlocked }
val hardcore = unlocked.count { it.unlockedMask and 2 != 0 }
val casual = unlocked.size - hardcore
return buildString {
append(raItems.count { it.unlocked }).append('/').append(raItems.size)
lastUnlock?.let { append('\n').append(it) }
append("🏆").append(hardcore)
if (casual > 0) append(" 🎖").append(casual)
append('\n').append(unlocked.size).append('/').append(raItems.size)
}
}