Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,6 @@ import com.getcode.navigation.results.rememberNavResultStateRegistry
import com.getcode.navigation.scenes.ModalBottomSheetSceneStrategy
import com.getcode.navigation.scrim.LocalScrimController
import com.getcode.navigation.scrim.ScrimController
import com.flipcash.app.bills.BillOverlay
import com.getcode.navigation.scrim.ScrimOverlay
import com.getcode.theme.CodeTheme
import com.getcode.ui.biometrics.LocalBiometricsState
import com.getcode.ui.biometrics.rememberBiometricsState
Expand Down Expand Up @@ -223,12 +221,12 @@ internal fun App(
)
}

ScrimOverlay(scrimController)

// Bills render at the app root (not inside the scanner) so a
// presented bill appears over any screen. Reads the app-scoped
// billState via LocalSessionController.
BillOverlay()
// The scrim + bill overlay are hosted per-entry by
// NavBillOverlayEntryDecorator (added to the AppNavHost
// decorators) rather than as app-root siblings — that keeps
// the bill above the current screen while letting a bottom
// sheet (hosted in the same NavDisplay) render ABOVE the
// bill. See NavBillOverlayEntryDecorator.
}

val emailCodeChannel = LocalEmailCodeChannel.current
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import com.flipcash.app.core.navigation.DeeplinkAction
import com.flipcash.app.core.navigation.asNavBarTab
import com.flipcash.app.core.navigation.LocalTabBarPadding
import com.flipcash.app.internal.ui.AppNavigationBar
import com.flipcash.app.internal.ui.navigation.decorators.rememberNavBillOverlayEntryDecorator
import com.flipcash.app.internal.ui.navigation.decorators.rememberNavBlockingOverlayEntryDecorator
import com.flipcash.app.internal.ui.navigation.decorators.rememberNavMessagingEntryDecorator
import com.getcode.navigation.AppNavHost
Expand All @@ -54,6 +55,10 @@ internal fun AppContent(
navigator = codeNavigator,
resultStateRegistry = resultStateRegistry,
decorators = listOf(
// First = outermost decorator overlay: the bill draws above the screen content (as it
// did at the app root). It's skipped for sheet entries, and NavDisplay paints the sheet
// scene above the base entry — so sheets open over the bill. See the decorator's docs.
rememberNavBillOverlayEntryDecorator(),
rememberNavMessagingEntryDecorator(
codeNavigator.backStack,
barManager
Expand Down Expand Up @@ -163,6 +168,10 @@ internal fun NewAppContent(
navigator = codeNavigator,
resultStateRegistry = resultStateRegistry,
decorators = listOf(
// First = outermost: bill draws above screen content but is skipped for sheet
// entries, so NavDisplay paints the sheet scene above the bill-bearing base
// entry — sheets open OVER the bill. See NavBillOverlayEntryDecorator.
rememberNavBillOverlayEntryDecorator(),
rememberNavMessagingEntryDecorator(
codeNavigator.backStack,
barManager
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.flipcash.app.internal.ui.navigation.decorators

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.navigation3.runtime.NavEntryDecorator
import androidx.navigation3.runtime.NavKey
import com.flipcash.app.bills.BillOverlay
import com.getcode.navigation.NavMetadataKeys
import com.getcode.navigation.scrim.LocalScrimController
import com.getcode.navigation.scrim.ScrimOverlay

/**
* Draws the app's bill overlay (tip card / payable) — and the scrim beneath it — on top of each
* NON-sheet navigation entry.
*
* Previously the bill was a sibling at the app root, painted *after* the [NavDisplay]; because a
* bottom sheet is hosted inside that same NavDisplay, the bill always covered it. Hosting the bill
* as a per-entry decorator instead means:
* - it still floats above the current screen (the decorator draws it over `entry.Content()`), but
* - it is skipped for the sheet entry, and [NavDisplay] paints the sheet's overlay scene *above*
* the base scene — so a sheet opened over a presented bill (the tip token / amount pickers)
* renders above the bill as a normal sheet, gestures intact, without a separate window.
*
* Opening a sheet doesn't change the base entry, so the bill's animation state stays stable through
* the interaction. The scrim ([LocalScrimController]) is drawn just beneath the bill so it keeps
* dimming the screen content below the bill rather than the bill itself.
*/
@Suppress("FunctionName")
fun NavBillOverlayEntryDecorator(): NavEntryDecorator<NavKey> {
return NavEntryDecorator { entry ->
Box(modifier = Modifier.fillMaxSize()) {
entry.Content()

// Sheets are painted above the base scene by NavDisplay, so they must NOT carry the
// bill themselves — otherwise the bill would cover the sheet again.
val isSheet = entry.metadata[NavMetadataKeys.IsSheet.key] == true
if (!isSheet) {
ScrimOverlay(LocalScrimController.current)
BillOverlay()
}
}
}
}

@Composable
fun rememberNavBillOverlayEntryDecorator() = remember { NavBillOverlayEntryDecorator() }
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.CircleShape
Expand Down Expand Up @@ -62,7 +63,7 @@ fun TokenSelectionPill(

Image(
modifier = Modifier
.width(CodeTheme.dimens.grid.x4),
.size(CodeTheme.dimens.grid.x4),
painter = painterResource(R.drawable.ic_dropdown),
contentDescription = ""
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,13 @@ fun BillOverlay(modifier: Modifier = Modifier) {
// shared root [ScrimController] (its ScrimOverlay sits just below this overlay), so it uses
// the theme scrim colour. Skipped over the scanner tab, where the live camera stays visible.
val scrimController = LocalScrimController.current
val overCamera =
(LocalCodeNavigator.current.currentRouteKey as? AppRoute)?.asNavBarTab() == NavBarButton.Scanner
// Resolve the tab UNDERNEATH any presented sheet, not the sheet route itself: a sheet opened
// over the bill (the tip token / amount pickers) pushes a route whose tab is null, which
// would flip this decision and fade the bill's scrim out/in around the sheet. Keying off the
// base tab keeps the scrim decision — and thus the scrim — stable across the sheet.
val baseRoute = LocalCodeNavigator.current.backStack
.lastOrNull { it !is AppRoute.Main.Sheet } as? AppRoute
val overCamera = baseRoute?.asNavBarTab() == NavBarButton.Scanner
val showScrim = billState.bill != null && !overCamera
LaunchedEffect(showScrim) {
if (showScrim) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,31 @@ internal fun TipUserModal(

Modal(
verticalArrangement = Arrangement.spacedBy(CodeTheme.dimens.inset),
contentPadding = PaddingValues(
start = CodeTheme.dimens.inset,
end = CodeTheme.dimens.inset,
top = CodeTheme.dimens.inset,
bottom = CodeTheme.dimens.grid.x2
),
) {
Text(
text = stringResource(id = R.string.title_sendTip),
style = CodeTheme.typography.displaySmall,
color = CodeTheme.colors.textMain,
)
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = stringResource(id = R.string.title_sendTip),
style = CodeTheme.typography.displaySmall,
color = CodeTheme.colors.textMain,
)

TipTokenRow(
token = selection.token,
onSelectToken = {
navigator.openAsSheet(AppRoute.Sheets.TokenSelection(TokenPurpose.Tip(selection.minimum)))
},
)
}

PresetOptions(
// Presets follow the selected region reactively (see TippingCoordinator.selection).
Expand All @@ -77,14 +96,6 @@ internal fun TipUserModal(
onCustomClicked = { navigator.openAsSheet(AppRoute.Sheets.TipAmountEntry) },
)

TipTokenRow(
token = selection.token,
modifier = Modifier.fillMaxWidth(),
onSelectToken = {
navigator.openAsSheet(AppRoute.Sheets.TokenSelection(TokenPurpose.Tip( selection.minimum)))
},
)

SlideToConfirm(
onConfirm = { tip.confirmTip() },
modifier = Modifier.fillMaxWidth(),
Expand All @@ -96,38 +107,24 @@ internal fun TipUserModal(
}
}

/** "of [token]" row — the token the tip will be sent in; tapping the pill opens the token picker. */
@Composable
private fun TipTokenRow(
token: Token?,
onSelectToken: () -> Unit,
modifier: Modifier = Modifier,
) {
Row(
TokenSelectionPill(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(
CodeTheme.dimens.grid.x2,
Alignment.CenterHorizontally
token = token,
background = White10,
textStyle = CodeTheme.typography.textMedium,
imageSize = CodeTheme.dimens.staticGrid.x3,
contentPadding = PaddingValues(
horizontal = CodeTheme.dimens.grid.x1 + 1.dp,
vertical = CodeTheme.dimens.grid.x1 - 1.dp,
),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = stringResource(R.string.label_of),
style = CodeTheme.typography.textMedium,
color = CodeTheme.colors.textSecondary,
)
TokenSelectionPill(
token = token,
background = White10,
textStyle = CodeTheme.typography.textMedium,
imageSize = CodeTheme.dimens.staticGrid.x3,
contentPadding = PaddingValues(
horizontal = CodeTheme.dimens.grid.x1 + 1.dp,
vertical = CodeTheme.dimens.grid.x1 - 1.dp,
),
onClick = onSelectToken,
)
}
onClick = onSelectToken,
)
}

@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,11 @@ internal class ModalBottomSheetScene<T : Any> constructor(
val progress = sheetState
.progress(SheetDetent.Hidden, Expanded)
.coerceIn(0f, 1f)
drawRect(scrimBaseColor.copy(alpha = scrimBaseColor.alpha * progress))
// A bill beneath the sheet already dims the screen with its own
// (constant) scrim, so don't stack a second dim here — otherwise
// opening the sheet would visibly darken the backdrop.
val alpha = if (scrim.visible) 0f else scrimBaseColor.alpha * progress
drawRect(scrimBaseColor.copy(alpha = alpha))
}
.then(
if (effectiveProperties.dismissOnClickOutside) {
Expand All @@ -212,7 +216,10 @@ internal class ModalBottomSheetScene<T : Any> constructor(
UnstyledBottomSheet(
state = sheetState,
modifier = Modifier.fillMaxSize(),
enabled = !navigator.sheetDragDisabled && !scrim.visible,
// Only block dragging for a scrim that puts CONTENT over the sheet; a plain
// bill dim beneath the sheet must not disable the sheet's own gestures.
enabled = !navigator.sheetDragDisabled &&
!(scrim.visible && scrim.overlayContent != null),
) {
Sheet(
modifier = Modifier
Expand All @@ -237,7 +244,11 @@ internal class ModalBottomSheetScene<T : Any> constructor(
) {
entry.Content()
}
if (!isWrapContent) {
// Only surface the shared scrim inside the sheet when it has
// CONTENT to show over it. A plain bill dim must not be redrawn
// here — it would double-dim the sheet and its tap-to-dismiss
// would close the bill from on top of the sheet.
if (!isWrapContent && scrim.overlayContent != null) {
ScrimOverlay(scrim)
}
}
Expand Down
Loading