Implement approved TV loading and bounded motion
All checks were successful
review/code-claude Code review passed on 16684454, no blocking finding
review/design-claude Design review pass: motion numbers equal direction A; 6 non-blocking notes

This commit is contained in:
justinzeus 2026-11-07 00:57:13 +01:00
parent fd48426fa7
commit 1668445418
86 changed files with 2026 additions and 121 deletions

View file

@ -15,7 +15,8 @@ import coil3.network.NetworkResponse
import coil3.network.cachecontrol.CacheControlCacheStrategy
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
import coil3.request.Options
import coil3.request.crossfade
import coil3.request.transitionFactory
import org.justinvisser.wolfskin.design.NetworkArtTransition
import coil3.util.DebugLogger
import com.github.damontecres.wholphin.preferences.AppPreference
import com.github.damontecres.wholphin.preferences.AppPreferences
@ -89,7 +90,7 @@ fun CoilConfig(
memoryCache(null)
diskCache(null)
}
}.crossfade(false)
}.transitionFactory(NetworkArtTransition)
.logger(if (debugLogging) DebugLogger() else null)
.components {
add(

View file

@ -0,0 +1,51 @@
package org.justinvisser.wolfskin.tv
import android.app.Application
import androidx.activity.ComponentActivity
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performKeyInput
import androidx.compose.ui.test.pressKey
import io.mockk.mockk
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import org.junit.Assert.*
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.justinvisser.wolfskin.design.*
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
/** A pending shelf cannot capture the D-pad or break the shell's Back ladder. */
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [34], application = Application::class, qualifiers = "w960dp-h540dp-xhdpi")
class DiscoverSkeletonKeysTest {
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
@Test fun downAndUpSkipPendingLanesAndBackReturnsToNavWhenOnlySkeletonsRemain() {
val shell = ShellState(listOf(ShellPerson("justin", "Justin", "J", false)), "justin").apply {
pickerVisible = false; pickerLaunch = false; enterDestination("discover")
}
val pending = DiscoverLane("pending", "Pending", emptyList(), pending = true)
val ready = DiscoverLane("ready", "Available", listOf(DiscoverTitle(1, "movie", "A real title")))
val snapshot = DiscoverSnapshot(status = DiscoverStatus.Ready, lanes = listOf(pending, ready))
val state = DiscoverState(shell, snapshot, mockk(relaxed = true), CoroutineScope(Dispatchers.Main.immediate), {}, {})
state.focus(1)
rule.setContent { WolfskinTheme { ShellSurface(shell) { DiscoverScreen(state, it) } } }
press(Key.DirectionDown)
rule.runOnIdle { assertEquals("ready", state.focusedLane?.id); assertEquals("ready:0", state.cardKey) }
press(Key.DirectionUp)
rule.runOnIdle {
assertEquals(1, state.focusedRow)
state.updateSnapshot(snapshot.copy(lanes = listOf(pending)))
}
press(Key.DirectionDown)
rule.runOnIdle { assertEquals(1, state.focusedRow); assertNull(state.cardKey) }
press(Key.Back)
rule.runOnIdle { assertTrue(shell.navFocused); assertFalse(shell.pickerVisible); assertEquals("discover", shell.route) }
}
private fun press(key: Key) { rule.onNodeWithTag("wolfskin-shell").performKeyInput { pressKey(key) } }
}