Preserve poster progress and focus across library rail changes
All checks were successful
review/code-gpt GPT review blockers fixed and read by firstmate: poster progress inset back to Title.cardProgressInset (220 px track) with test tag + regression; library focus-follow effect keyed on columns/focusedRow/header/rowHeight with a 199<->200 regression; token values/ring bounds/toolbar gap passed review on c17447c5; walked on the real box
All checks were successful
review/code-gpt GPT review blockers fixed and read by firstmate: poster progress inset back to Title.cardProgressInset (220 px track) with test tag + regression; library focus-follow effect keyed on columns/focusedRow/header/rowHeight with a 199<->200 regression; token values/ring bounds/toolbar gap passed review on c17447c5; walked on the real box
This commit is contained in:
parent
b9ea593735
commit
92c5661be0
4 changed files with 75 additions and 12 deletions
|
|
@ -41,8 +41,8 @@ fun LibraryPoster(
|
|||
if (title.ratings.isNotEmpty()) LibraryRatingPlate(title.ratings, visible = ratingsVisible)
|
||||
val progress = title.progress
|
||||
if (progress > 0f && progress < 1f) {
|
||||
Box(Modifier.align(Alignment.BottomCenter).padding(horizontal = WolfskinTokens.Layout.captionOffset, vertical = WolfskinTokens.Layout.navItemGap)
|
||||
.fillMaxWidth().height(WolfskinTokens.Layout.progressBar).background(C.progressTrack, CircleShape)) {
|
||||
Box(Modifier.align(Alignment.BottomCenter).padding(horizontal = WolfskinTokens.Title.cardProgressInset, vertical = WolfskinTokens.Layout.navItemGap)
|
||||
.fillMaxWidth().height(WolfskinTokens.Layout.progressBar).testTag("poster-progress-${title.id}").background(C.progressTrack, CircleShape)) {
|
||||
Box(Modifier.fillMaxHeight().fillMaxWidth(progress).background(C.accent, CircleShape))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,13 +88,13 @@ fun LibraryScreen(
|
|||
}
|
||||
val headerCount = if (grid.hasToolbar && grid.status != LibraryStatus.Loading && grid.status != LibraryStatus.Unavailable) 2 else 1
|
||||
val rowHeight = K.posterH + K.posterCaptionBand + L.gridRowGap
|
||||
LaunchedEffect(grid.index, grid.toolbar, grid.inRail, grid.railLetter, shell.navFocused, view) {
|
||||
val index = if (grid.inRail) items.indexOfFirst { it.letter == grid.railLetter } else grid.index
|
||||
val row = index.coerceAtLeast(0) / grid.columns
|
||||
if (grid.toolbar || row == 0 && active) {
|
||||
val focusedRow = (if (grid.inRail) items.indexOfFirst { it.letter == grid.railLetter } else grid.index).coerceAtLeast(0) / grid.columns
|
||||
LaunchedEffect(grid.index, grid.columns, focusedRow, headerCount, rowHeight, density,
|
||||
grid.toolbar, grid.inRail, grid.railLetter, shell.navFocused, view) {
|
||||
if (grid.toolbar || focusedRow == 0 && active) {
|
||||
animateLibraryScroll(view, headerCount, 0f, density)
|
||||
} else if (row > 0) {
|
||||
val target = with(density) { (L.row_lhead + (if (headerCount == 2) L.row_chipsGrid else WolfskinTokens.Shell.zero) + rowHeight * row - L.pinnedLine).toPx() }
|
||||
} else if (focusedRow > 0) {
|
||||
val target = with(density) { (L.row_lhead + (if (headerCount == 2) L.row_chipsGrid else WolfskinTokens.Shell.zero) + rowHeight * focusedRow - L.pinnedLine).toPx() }
|
||||
animateLibraryScroll(view, headerCount, target, density)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
package org.justinvisser.wolfskin.design
|
||||
|
||||
import android.app.Application
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
/** Caption spacing and live rail changes must preserve poster geometry and retained focus. */
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [34], application = Application::class, qualifiers = "w960dp-h540dp-xhdpi")
|
||||
class LibrarySpacingRegressionTest {
|
||||
@get:Rule val rule = createComposeRule()
|
||||
|
||||
@Test fun partiallyWatchedPosterKeepsItsOriginalProgressInsets() {
|
||||
val title = LibraryTitle("partial", "Partially watched", "movie", progress = .5f)
|
||||
rule.setContent { WolfskinTheme { LibraryPoster(title, false, false, false) } }
|
||||
val poster = rule.onNodeWithTag("poster-partial").fetchSemanticsNode().boundsInRoot
|
||||
val track = rule.onNodeWithTag("poster-progress-partial").fetchSemanticsNode().boundsInRoot
|
||||
assertEquals(256f, poster.width, .1f)
|
||||
assertEquals(220f, track.width, .1f)
|
||||
assertEquals(18f, track.left - poster.left, .1f)
|
||||
assertEquals(18f, poster.right - track.right, .1f)
|
||||
}
|
||||
|
||||
@Test fun crossingRailThresholdKeepsTheSameDeepFocusedPosterVisible() {
|
||||
val titles = List(200) { LibraryTitle("$it", "Title ${it.toString().padStart(3, '0')}", "tv") }
|
||||
val shell = ShellState(fixturePeople(), "justin").apply { pickerVisible = false; enterDestination("shows") }
|
||||
val grid = LibraryGridState("Shows", titles.take(199)).apply { index = 198 }
|
||||
rule.setContent { WolfskinTheme { ShellSurface(shell) { LibraryScreen(shell, grid, it) } } }
|
||||
rule.mainClock.advanceTimeBy(1000)
|
||||
rule.waitForIdle()
|
||||
val poster = rule.onNodeWithTag("poster-198")
|
||||
poster.assertIsDisplayed()
|
||||
val original = poster.fetchSemanticsNode().boundsInRoot
|
||||
for (count in listOf(200, 199)) {
|
||||
rule.runOnIdle { grid.source = titles.take(count) }
|
||||
rule.mainClock.advanceTimeBy(1000)
|
||||
rule.waitForIdle()
|
||||
poster.assertIsDisplayed()
|
||||
val current = poster.fetchSemanticsNode().boundsInRoot
|
||||
assertEquals(198, grid.index)
|
||||
assertEquals("198", grid.focusedTitle!!.id)
|
||||
assertEquals(original.top, current.top, 1f)
|
||||
assertEquals(original.height, current.height, 1f)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue