From f6bd69ed00bd3350c16c828ace55b87c7ec7b6b6 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Thu, 15 Oct 2026 08:32:16 -0400 Subject: [PATCH 1/2] fix(navbar): keep unselected tab glyphs and labels white Unselected tabs dimmed their glyph and label to 50%. iOS 27 paints unselected tab glyphs in the main text color, so the outline glyph and the indicator pill now carry the selection on their own. The photo slot keeps its own unselected state (node 9713:664). --- .../com/flipcash/app/core/ui/NavigationBar.kt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/NavigationBar.kt b/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/NavigationBar.kt index e55d15636..656f6e47c 100644 --- a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/NavigationBar.kt +++ b/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/NavigationBar.kt @@ -159,6 +159,9 @@ fun NavigationBar( Row(modifier = Modifier.fillMaxWidth()) { order.fastForEach { button -> val selected = button == state.selectedTab + // Only the photo slot dims when unselected. Glyphs and labels stay white in both + // states, as the iOS 27 tab bar paints them: the outline-vs-filled glyph and the + // indicator pill carry the selection. val iconAlpha by animateFloatAsState( targetValue = if (selected) 1f else 0.5f, label = "navBarIconAlpha", @@ -221,9 +224,7 @@ fun NavigationBar( } } else { Image( - modifier = Modifier - .size(iconSize) - .graphicsLayer { alpha = iconAlpha }, + modifier = Modifier.size(iconSize), painter = button.icon(selected), colorFilter = ColorFilter.tint(Color.White), contentDescription = null, @@ -253,7 +254,6 @@ fun NavigationBar( color = Color.White, textAlign = TextAlign.Center, maxLines = 1, - modifier = Modifier.graphicsLayer { alpha = iconAlpha }, ) } } @@ -285,9 +285,8 @@ private val NavBarButton.label: Int /** * The glyph for [this] tab at the weight its selection calls for (node 10000:111297). Every tab is - * drawn as an outline until it is selected, where it fills in; the dimming on top of that is the - * caller's alpha. The unsuffixed drawable is the outline, so `ic_nav_tipcard` is also the outline - * the tutorial card reuses. + * drawn as an outline until it is selected, where it fills in, white in both states. The unsuffixed + * drawable is the outline, so `ic_nav_tipcard` is also the outline the tutorial card reuses. * * The scanner tab carries the tip card glyph because scanning one is what that tab is for. The You * tab draws the user's own photo when there is one (see the avatar branch above) and falls back to From 736d9ab920b4fef79f208ab5e446d5d4289e985a Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Thu, 15 Oct 2026 08:54:31 -0400 Subject: [PATCH 2/2] fix(messenger): stub newestPageLoads in ChatTypingGateTest #1783 made opening a chat wait on newestPageLoads().first { }. The test's relaxed ChatCoordinator returns a Flow that completes empty, so first threw NoSuchElementException and both tests failed on code/cash. Stub it with a MutableSharedFlow, as ChatOpenTranscriptTest does. --- .../com/flipcash/app/messenger/internal/ChatTypingGateTest.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/flipcash/features/messenger/src/test/kotlin/com/flipcash/app/messenger/internal/ChatTypingGateTest.kt b/apps/flipcash/features/messenger/src/test/kotlin/com/flipcash/app/messenger/internal/ChatTypingGateTest.kt index b697912ee..7af68648a 100644 --- a/apps/flipcash/features/messenger/src/test/kotlin/com/flipcash/app/messenger/internal/ChatTypingGateTest.kt +++ b/apps/flipcash/features/messenger/src/test/kotlin/com/flipcash/app/messenger/internal/ChatTypingGateTest.kt @@ -28,6 +28,7 @@ import io.mockk.coEvery import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf import kotlin.test.assertFalse @@ -57,6 +58,8 @@ class ChatTypingGateTest { private val chatCoordinator = mockk(relaxed = true) { every { observeMediaSendProgress() } returns kotlinx.coroutines.flow.emptyFlow() + // No load ever lands; a relaxed Flow completes empty and fails the open's first { }. + every { newestPageLoads() } returns MutableSharedFlow() } private val tipPaymentDelegate = mockk(relaxed = true) private val transactionController = mockk(relaxed = true)