Merge pull request 'Restore household colours on the cold-start profile picker' (#70) from fm/tv-picker-colors into fm/tv-round2
This commit is contained in:
commit
2c5e39261c
3 changed files with 34 additions and 1 deletions
|
|
@ -2,6 +2,7 @@
|
||||||
package org.justinvisser.wolfskin.tv
|
package org.justinvisser.wolfskin.tv
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
|
@ -11,6 +12,7 @@ import androidx.compose.runtime.mutableStateListOf
|
||||||
import androidx.compose.runtime.snapshots.Snapshot
|
import androidx.compose.runtime.snapshots.Snapshot
|
||||||
import org.robolectric.Shadows
|
import org.robolectric.Shadows
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.test.*
|
import androidx.compose.ui.test.*
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||||
import androidx.datastore.core.DataStore
|
import androidx.datastore.core.DataStore
|
||||||
|
|
@ -46,6 +48,7 @@ import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.justinvisser.wolfskin.conductarr.MemoryCacheStore
|
import org.justinvisser.wolfskin.conductarr.MemoryCacheStore
|
||||||
import org.robolectric.RobolectricTestRunner
|
import org.robolectric.RobolectricTestRunner
|
||||||
|
import org.justinvisser.wolfskin.design.WolfskinTokens
|
||||||
import org.robolectric.annotation.Config
|
import org.robolectric.annotation.Config
|
||||||
import org.robolectric.annotation.GraphicsMode
|
import org.robolectric.annotation.GraphicsMode
|
||||||
|
|
||||||
|
|
@ -137,6 +140,34 @@ class WolfskinSetupNavigationTest {
|
||||||
coVerify(exactly = 0) { repository.switchServerOrUser() }
|
coVerify(exactly = 0) { repository.switchServerOrUser() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GraphicsMode(GraphicsMode.Mode.NATIVE)
|
||||||
|
@Test fun coldStartSavedPeopleKeepTheirOwnColorsWithoutConductarr() {
|
||||||
|
val saved = listOf(user, user.copy(id = UUID.randomUUID(), name = "Mindel"),
|
||||||
|
user.copy(id = UUID.randomUUID(), name = "Shared"))
|
||||||
|
every { repository.serverDao.getServer(server.id) } returns JellyfinServerUsers(server, saved)
|
||||||
|
runBlocking { appModel().handleAppStart() }
|
||||||
|
mount()
|
||||||
|
assertOwnPicker()
|
||||||
|
saved.forEach { rule.onNodeWithText(it.name!!).assertIsDisplayed() }
|
||||||
|
rule.mainClock.advanceTimeBy(1_000)
|
||||||
|
rule.waitForIdle()
|
||||||
|
val people = model.picker!!.people
|
||||||
|
val bounds = people.map { rule.onNodeWithTag("picker-person:${it.key}").fetchSemanticsNode().boundsInRoot }
|
||||||
|
val directory = File(requireNotNull(System.getProperty("wolfskin.captureDirectory")), "setup").apply { mkdirs() }
|
||||||
|
val frame = File(directory, "cold-start-people.png")
|
||||||
|
rule.onRoot().captureRoboImage(frame)
|
||||||
|
val bitmap = BitmapFactory.decodeFile(frame.absolutePath)
|
||||||
|
try {
|
||||||
|
val fills = bounds.map { bitmap.getPixel(it.center.x.toInt(), (it.top + it.width / 5).toInt()) }
|
||||||
|
assertEquals(listOf(WolfskinTokens.PersonColor.justin.toArgb(),
|
||||||
|
WolfskinTokens.PersonColor.mindel.toArgb(), WolfskinTokens.PersonColor.shared.toArgb()), fills)
|
||||||
|
} finally {
|
||||||
|
bitmap.recycle()
|
||||||
|
}
|
||||||
|
assertNull(conductarr.identity.value)
|
||||||
|
assertEquals(saved.map { it.id.toString() }, people.map { it.jellyfinUserId })
|
||||||
|
}
|
||||||
|
|
||||||
@GraphicsMode(GraphicsMode.Mode.NATIVE)
|
@GraphicsMode(GraphicsMode.Mode.NATIVE)
|
||||||
@Test fun unreachableSelectionRendersWolfskinRecoveryFrame() {
|
@Test fun unreachableSelectionRendersWolfskinRecoveryFrame() {
|
||||||
stack[0] = SetupDestination.UserList(server)
|
stack[0] = SetupDestination.UserList(server)
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ class WolfskinSetupViewModel @Inject constructor(
|
||||||
if (users.isEmpty()) {
|
if (users.isEmpty()) {
|
||||||
showLogin(null)
|
showLogin(null)
|
||||||
} else {
|
} else {
|
||||||
val people = users.map { ShellPerson(it.id.toString(), it.name.orEmpty(), it.name.orEmpty().take(1).uppercase(), false, it.id.toString()) }
|
val people = users.map { ShellPerson(it.name.orEmpty().lowercase(), it.name.orEmpty(), it.name.orEmpty().take(1).uppercase(), false, it.id.toString()) }
|
||||||
picker = ShellState(people, people.first().key) { onAction(it) }
|
picker = ShellState(people, people.first().key) { onAction(it) }
|
||||||
stage = Stage.Picker
|
stage = Stage.Picker
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ Down from any avatar reaches the gear; Up returns to its origin, including after
|
||||||
|
|
||||||
Source — Justin (2026-11-07): “the settings icon moved, but pressing down only works from the last profile icon. I want it to always move there when pressing down in that screen”.
|
Source — Justin (2026-11-07): “the settings icon moved, but pressing down only works from the last profile icon. I want it to always move there when pressing down in that screen”.
|
||||||
|
|
||||||
|
Saved profiles retain their approved household colours before Conductarr answers: Justin **#62c4ff**, Mindel **#ff9d72**, Shared **#9be38f**. Setup uses the same lowercase-name presentation key as the signed-in picker; the separate Jellyfin user ID remains the authentication identity. `WolfskinSetupNavigationTest.coldStartSavedPeopleKeepTheirOwnColorsWithoutConductarr` exercises the real cold-start setup boundary and checks the native avatar-fill pixels without a Conductarr session.
|
||||||
|
|
||||||
Root content Back moves to the nav; the next Back **finishes and removes Wolfskin’s Android task**, returning to the launcher without visiting an old Home/setup interface. This applies to Home, Discover, Movies, Shows and Anime. Cold-launch picker Back exits; a reopened picker first dismisses to its opener’s nav. Pushed title/person/settings/Updates pages pop their parent stack, and menus/sheets/unsaved-settings prompts get first refusal. A singleton full-screen page exits rather than becoming a dead end.
|
Root content Back moves to the nav; the next Back **finishes and removes Wolfskin’s Android task**, returning to the launcher without visiting an old Home/setup interface. This applies to Home, Discover, Movies, Shows and Anime. Cold-launch picker Back exits; a reopened picker first dismisses to its opener’s nav. Pushed title/person/settings/Updates pages pop their parent stack, and menus/sheets/unsaved-settings prompts get first refusal. A singleton full-screen page exits rather than becoming a dead end.
|
||||||
|
|
||||||
Native Android Back (`KEYCODE_BACK`) belongs exclusively to the activity’s system dispatcher, including Navigation 3 predictive completion; Compose key previews must not also navigate on its raw key-down. Escape remains the existing local keyboard Back shortcut. Each completed system Back has one consumer. A retained title/person callback checks the live navigation entry at invocation and hands Back to the incoming entry if its own entry has already popped; it does not depend on an enabled flag catching up with the exit animation. Root handling synchronizes the shell route to the current entry before applying content → nav → task exit. Back ownership is exercised on both SDK 34 (Android 14) and SDK 36.
|
Native Android Back (`KEYCODE_BACK`) belongs exclusively to the activity’s system dispatcher, including Navigation 3 predictive completion; Compose key previews must not also navigate on its raw key-down. Escape remains the existing local keyboard Back shortcut. Each completed system Back has one consumer. A retained title/person callback checks the live navigation entry at invocation and hands Back to the incoming entry if its own entry has already popped; it does not depend on an enabled flag catching up with the exit animation. Root handling synchronizes the shell route to the current entry before applying content → nav → task exit. Back ownership is exercised on both SDK 34 (Android 14) and SDK 36.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue