0.3.20
This commit is contained in:
@@ -38,7 +38,7 @@ val membyGatewayUrl: String = (project.findProperty("memby.gatewayUrl") as Strin
|
||||
val membyDiagnosticLogLevel: String = (project.findProperty("memby.diagnosticLogLevel") as String?)
|
||||
?.trim()?.uppercase()?.takeIf { it in setOf("INFO", "DEBUG", "TRACE") } ?: "INFO"
|
||||
|
||||
val defaultVersionName = "0.3.18"
|
||||
val defaultVersionName = "0.3.20"
|
||||
val membyVersionName: String =
|
||||
(project.findProperty("memby.versionName") as String?)
|
||||
?.trim()
|
||||
|
||||
@@ -1266,6 +1266,19 @@ internal fun HomeScreen(
|
||||
val firstPopulatedRowId = remember(rows) {
|
||||
rows.firstOrNull { candidate -> candidate.items.isNotEmpty() }?.id
|
||||
}
|
||||
// The metadata panel only needs to know whether the focused row is
|
||||
// Continue Watching, and that answer changes when the viewer crosses into
|
||||
// or out of one row. Reading focusedHomeRowId where the panel is composed
|
||||
// read it in the scope that also declares the LazyColumn below, so every
|
||||
// vertical press invalidated the whole launcher and re-declared the row
|
||||
// list. Derived, the scope is invalidated only when the boolean actually
|
||||
// flips.
|
||||
val focusedRowIsContinueWatching by remember(rows) {
|
||||
derivedStateOf {
|
||||
rows.firstOrNull { it.id == focusedHomeRowId }?.kind ==
|
||||
MediaRowKind.CONTINUE
|
||||
}
|
||||
}
|
||||
// My Shows is a band of its own above the shelves, so on this page it is the
|
||||
// first stop below the hero and the thing Up out of the topmost shelf has to
|
||||
// return to. Both moves used to jump over it: the hero pointed Down straight
|
||||
@@ -1404,9 +1417,7 @@ internal fun HomeScreen(
|
||||
homeViewModel = homeViewModel,
|
||||
metadataHeroContentOrder = metadataHeroContentOrder,
|
||||
metadataHeroTimeRemainingColour = metadataHeroTimeRemainingColour,
|
||||
isContinueWatchingItem = rows.firstOrNull {
|
||||
it.id == focusedHomeRowId
|
||||
}?.kind == MediaRowKind.CONTINUE,
|
||||
isContinueWatchingItem = focusedRowIsContinueWatching,
|
||||
modifier = Modifier
|
||||
.height(metadataHeight)
|
||||
// LazyColumn is drawn later as a sibling. Keep the hero
|
||||
@@ -1540,6 +1551,29 @@ internal fun HomeScreen(
|
||||
row.items.take(8).map { it.id },
|
||||
)
|
||||
}
|
||||
// Only the destination row can act on a vertical focus request,
|
||||
// but a plain read of pendingRowFocus here happened in every
|
||||
// composed row's scope — so one press recomposed all of them
|
||||
// twice, once as the request was posted and again as it was
|
||||
// consumed and set back to null. Derived per row, a row whose
|
||||
// id does not match sees null both times and is never
|
||||
// invalidated; the destination row behaves exactly as before.
|
||||
val rowFocusRequest by remember(row.id) {
|
||||
derivedStateOf {
|
||||
pendingRowFocus?.takeIf { it.rowId == row.id }
|
||||
}
|
||||
}
|
||||
// returnItemId and returnRowId are rewritten by *every* card
|
||||
// taking focus, horizontal travel included, and reading them
|
||||
// here read them in every composed row's scope — so moving one
|
||||
// card along a shelf recomposed every other shelf on screen.
|
||||
// Derived, a row that does not hold the return target sees null
|
||||
// and stays untouched.
|
||||
val rowReturnFocusItemId by remember(row.id) {
|
||||
derivedStateOf {
|
||||
returnItemId.takeIf { returnRowId == row.id }
|
||||
}
|
||||
}
|
||||
MediaRow(
|
||||
modifier = Modifier,
|
||||
row = row,
|
||||
@@ -1553,9 +1587,9 @@ internal fun HomeScreen(
|
||||
heroEntryFocusRequester = heroRowEntryFocusRequester.takeIf {
|
||||
hasContextualHero && row.id == firstPopulatedRowId
|
||||
},
|
||||
returnFocusItemId = returnItemId.takeIf { returnRowId == row.id },
|
||||
returnFocusItemId = rowReturnFocusItemId,
|
||||
returnFocusRequester = cardReturnFocusRequester,
|
||||
verticalFocusRequest = pendingRowFocus,
|
||||
verticalFocusRequest = rowFocusRequest,
|
||||
onVerticalFocusRequestConsumed = { requestId ->
|
||||
if (pendingRowFocus?.requestId == requestId) {
|
||||
pendingRowFocus = null
|
||||
|
||||
@@ -151,6 +151,8 @@ fun LandscapeCard(
|
||||
MediaCard(
|
||||
model, item, width, 16f / 9f, preferPrimary = false, showProgress = false,
|
||||
onFocused, onClick, onLongClick, modifier,
|
||||
// A shelf card sits under the metadata hero, which names the focused title already.
|
||||
showTitle = false,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -181,6 +181,8 @@ fun PosterCard(
|
||||
MediaCard(
|
||||
model, item, width, 2f / 3f, preferPrimary = true, showProgress,
|
||||
onFocused, onClick, onLongClick, modifier,
|
||||
// A shelf card sits under the metadata hero, which names the focused title already.
|
||||
showTitle = false,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -252,6 +254,18 @@ internal fun MediaCard(
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit,
|
||||
modifier: Modifier,
|
||||
/**
|
||||
* Whether the card captions itself with the title.
|
||||
*
|
||||
* False on the launcher's shelves, where the metadata hero above them is already
|
||||
* naming whatever holds focus — as the title treatment where there is a logo, as plain
|
||||
* text where there is not — so a caption there was the same title said twice, once in
|
||||
* the one place the eye is already reading it. True everywhere the card stands on its
|
||||
* own with no hero over it, which today is the genre grid: nothing else there says what
|
||||
* a poster is. The name is in [cardDescription] either way, so a screen reader is told
|
||||
* regardless of what is drawn.
|
||||
*/
|
||||
showTitle: Boolean = true,
|
||||
) {
|
||||
val density = LocalDensity.current
|
||||
val widthPx = with(density) { width.roundToPx() }.coerceIn(180, 720)
|
||||
@@ -386,6 +400,7 @@ internal fun MediaCard(
|
||||
)
|
||||
}
|
||||
}
|
||||
if (showTitle) {
|
||||
Text(
|
||||
model.name,
|
||||
color = MembyOnSurface,
|
||||
@@ -395,6 +410,7 @@ internal fun MediaCard(
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.padding(top = 9.dp).fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
if (model.showSecondaryMetadata) {
|
||||
// The media type sits *in* the metadata line rather than over the poster:
|
||||
// it is informational, like the year and the runtime beside it, and the
|
||||
@@ -403,7 +419,11 @@ internal fun MediaCard(
|
||||
// it cannot make the card taller than one without it.
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(top = 4.dp).fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
// With no title above it this line is the first thing under the
|
||||
// artwork, so it takes over the gap the title was holding.
|
||||
.padding(top = if (showTitle) 4.dp else 9.dp)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
model.typeMark?.let { (icon, _) ->
|
||||
Icon(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+21
-1
@@ -39,13 +39,33 @@ class BaselineProfileGenerator {
|
||||
browseHome()
|
||||
}
|
||||
|
||||
/**
|
||||
* Walks the launcher the way a viewer does, and walks it *vertically*.
|
||||
*
|
||||
* Two Down presses were not enough. Moving between shelves is the most expensive thing
|
||||
* the launcher does — a row is composed as it scrolls in, the focus request is posted
|
||||
* and consumed, and the hero catches up behind it — and none of that was in the profile,
|
||||
* so on a television it ran interpreted. Measured on a Chromecast with Google TV, that
|
||||
* was the difference between a 72ms and a 10ms recomposition at the 90th percentile.
|
||||
*
|
||||
* The walk goes down far enough to compose shelves beyond the first viewport and back up
|
||||
* again, because entering a row from below is a different path from entering it from
|
||||
* above, and both are on the D-pad.
|
||||
*/
|
||||
private fun MacrobenchmarkScope.browseHome() {
|
||||
repeat(4) { device.pressKeyCode(KeyEvent.KEYCODE_DPAD_RIGHT) }
|
||||
device.waitForIdle()
|
||||
repeat(6) {
|
||||
device.pressKeyCode(KeyEvent.KEYCODE_DPAD_DOWN)
|
||||
device.waitForIdle()
|
||||
}
|
||||
// Travelling a shelf is what pulls in the card, artwork and metadata composables
|
||||
// for a row that was composed by a vertical move rather than by the first frame.
|
||||
repeat(3) { device.pressKeyCode(KeyEvent.KEYCODE_DPAD_RIGHT) }
|
||||
device.waitForIdle()
|
||||
device.pressKeyCode(KeyEvent.KEYCODE_DPAD_DOWN)
|
||||
repeat(6) {
|
||||
device.pressKeyCode(KeyEvent.KEYCODE_DPAD_UP)
|
||||
device.waitForIdle()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user