Fix card clipping and table reshape; add a live seat-layout switcher
Three things, two of them defects rather than taste. Cards were double-clipped. CardImage applied a 6dp RoundedCornerShape clip over artwork that already carries its own 4.8dp radius and a black border, so the clip cut inside the card's own corner and shaved the border off. The clip and its backing plate are gone; the art is drawn at the same 2:3 ratio it is laid out at, so nothing is masked. The felt reshaped between circular and oval on every raise. PokerTable is weighted against the control column, so growing that column to hold the sizing controls shrank the table and rounded the oval. Raise sizing is now a sheet overlaid on the felt, which is what it was meant to be. Measured before and after opening it: felt top 451 both, identical width, height within 7dp — the remainder is the sheet's own gradient, not a reshape. Seat layout is now switchable at runtime via a SEATS chip in the header, so treatments can be compared on a real device instead of argued about from screenshots: - Plate: avatar and fan over a full-width name plate. Most information, widest. - Badge: one horizontal pill, avatar beside name and stack. Half the height. - Chip: avatar-led with name and stack beneath. Narrowest, most open felt. Seat width is derived per style, so each variant keeps the arc geometry and overflow guarantees the tests already enforce. 259 tests, 0 failures. Lint 0 errors. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import androidx.activity.compose.BackHandler
|
|||||||
import androidx.compose.foundation.Canvas
|
import androidx.compose.foundation.Canvas
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.horizontalScroll
|
import androidx.compose.foundation.horizontalScroll
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@@ -158,8 +159,31 @@ internal const val BOARD_Y_FRACTION = 0.47f
|
|||||||
* merely looking tight at 427dp (Pixel emulator). Sizing from the container
|
* merely looking tight at 427dp (Pixel emulator). Sizing from the container
|
||||||
* makes five seats fit at any width by construction.
|
* makes five seats fit at any width by construction.
|
||||||
*/
|
*/
|
||||||
internal fun opponentSeatWidthDp(availableWidthDp: Float): Float =
|
/**
|
||||||
(availableWidthDp * 0.16f).coerceIn(52f, 72f)
|
* Competing treatments for an opponent seat, switchable at runtime so they can
|
||||||
|
* be compared on a real device instead of argued about from screenshots.
|
||||||
|
*/
|
||||||
|
enum class SeatStyle(val label: String) {
|
||||||
|
/** Avatar and fan stacked over a full-width name plate. Most information. */
|
||||||
|
PLATE("Plate"),
|
||||||
|
|
||||||
|
/** One horizontal pill: avatar beside a compact name and stack. */
|
||||||
|
BADGE("Badge"),
|
||||||
|
|
||||||
|
/** Avatar-led, name and stack tucked under it. Least width. */
|
||||||
|
CHIP("Chip");
|
||||||
|
|
||||||
|
fun next(): SeatStyle = entries[(ordinal + 1) % entries.size]
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun opponentSeatWidthDp(
|
||||||
|
availableWidthDp: Float,
|
||||||
|
style: SeatStyle = SeatStyle.PLATE,
|
||||||
|
): Float = when (style) {
|
||||||
|
SeatStyle.PLATE -> (availableWidthDp * 0.16f).coerceIn(52f, 72f)
|
||||||
|
SeatStyle.BADGE -> (availableWidthDp * 0.26f).coerceIn(88f, 112f)
|
||||||
|
SeatStyle.CHIP -> (availableWidthDp * 0.13f).coerceIn(44f, 58f)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Five seats spread edge to edge with equal gaps, staggered into three lanes so
|
* Five seats spread edge to edge with equal gaps, staggered into three lanes so
|
||||||
@@ -206,20 +230,33 @@ fun TableScreen(vm: PokerViewModel) {
|
|||||||
val tableTalk = state.visibleTableTalk()
|
val tableTalk = state.visibleTableTalk()
|
||||||
val status = snap?.let(::tableStatus)
|
val status = snap?.let(::tableStatus)
|
||||||
|
|
||||||
Column(
|
// Raise sizing is hoisted here so it can overlay the felt. Left inside the
|
||||||
|
// control column it grew the controls, which shrank the weighted table and
|
||||||
|
// visibly reshaped the oval into a circle every time Raise was tapped.
|
||||||
|
var sizingRaise by remember(offer?.token) { mutableStateOf(false) }
|
||||||
|
var seatStyle by remember { mutableStateOf(SeatStyle.PLATE) }
|
||||||
|
|
||||||
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(Night)
|
.background(Night)
|
||||||
.systemBarsPadding(),
|
.systemBarsPadding(),
|
||||||
) {
|
) {
|
||||||
TableHeader(snap, state.gameConfig)
|
Column(modifier = Modifier.fillMaxSize()) {
|
||||||
|
TableHeader(
|
||||||
|
snapshot = snap,
|
||||||
|
config = state.gameConfig,
|
||||||
|
seatStyle = seatStyle,
|
||||||
|
onCycleSeatStyle = { seatStyle = seatStyle.next() },
|
||||||
|
)
|
||||||
PokerTable(
|
PokerTable(
|
||||||
snapshot = snap,
|
snapshot = snap,
|
||||||
status = status,
|
status = status,
|
||||||
tableTalk = tableTalk,
|
tableTalk = tableTalk,
|
||||||
handSummary = handSummary,
|
handSummary = handSummary,
|
||||||
// Fill the space the two-stage raise control freed instead of locking
|
seatStyle = seatStyle,
|
||||||
// an aspect ratio and leaving ~178dp of dead felt below the hero.
|
// Weighted against a control area whose height no longer changes,
|
||||||
|
// so the felt keeps one shape for the whole hand.
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.weight(1f),
|
.weight(1f),
|
||||||
@@ -234,16 +271,31 @@ fun TableScreen(vm: PokerViewModel) {
|
|||||||
heroFolded = snap?.seats?.firstOrNull { it.index == HERO_SEAT }?.folded == true,
|
heroFolded = snap?.seats?.firstOrNull { it.index == HERO_SEAT }?.folded == true,
|
||||||
onFold = vm::fold,
|
onFold = vm::fold,
|
||||||
onCheckCall = vm::checkOrCall,
|
onCheckCall = vm::checkOrCall,
|
||||||
onRaise = vm::raiseTo,
|
onOpenRaise = { sizingRaise = true },
|
||||||
onNextHand = vm::nextHand,
|
onNextHand = vm::nextHand,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sizingRaise && offer != null) {
|
||||||
|
RaiseSizingSheet(
|
||||||
|
offer = offer,
|
||||||
|
onDismiss = { sizingRaise = false },
|
||||||
|
onRaise = { token, amount ->
|
||||||
|
sizingRaise = false
|
||||||
|
vm.raiseTo(token, amount)
|
||||||
|
},
|
||||||
|
modifier = Modifier.align(Alignment.BottomCenter),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun TableHeader(
|
private fun TableHeader(
|
||||||
snapshot: TableSnapshot?,
|
snapshot: TableSnapshot?,
|
||||||
config: CashGameConfig?,
|
config: CashGameConfig?,
|
||||||
|
seatStyle: SeatStyle,
|
||||||
|
onCycleSeatStyle: () -> Unit,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -253,7 +305,10 @@ private fun TableHeader(
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
) {
|
) {
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(1.dp)) {
|
Column(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(1.dp),
|
||||||
|
) {
|
||||||
Text(
|
Text(
|
||||||
"HAND ${snapshot?.handNumber ?: 1}",
|
"HAND ${snapshot?.handNumber ?: 1}",
|
||||||
color = Cream,
|
color = Cream,
|
||||||
@@ -268,6 +323,26 @@ private fun TableHeader(
|
|||||||
letterSpacing = 1.4.sp,
|
letterSpacing = 1.4.sp,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 8.dp)
|
||||||
|
.clickable(onClick = onCycleSeatStyle),
|
||||||
|
shape = RoundedCornerShape(50),
|
||||||
|
color = Color.White.copy(alpha = 0.10f),
|
||||||
|
border = androidx.compose.foundation.BorderStroke(
|
||||||
|
1.dp,
|
||||||
|
TableGold.copy(alpha = 0.45f),
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"SEATS · ${seatStyle.label.uppercase()}",
|
||||||
|
modifier = Modifier.padding(horizontal = 10.dp, vertical = 5.dp),
|
||||||
|
color = TableGold,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 10.sp,
|
||||||
|
letterSpacing = 1.sp,
|
||||||
|
)
|
||||||
|
}
|
||||||
Surface(
|
Surface(
|
||||||
shape = RoundedCornerShape(50),
|
shape = RoundedCornerShape(50),
|
||||||
color = Color.Black.copy(alpha = 0.24f),
|
color = Color.Black.copy(alpha = 0.24f),
|
||||||
@@ -297,6 +372,7 @@ private fun PokerTable(
|
|||||||
status: TableStatus?,
|
status: TableStatus?,
|
||||||
tableTalk: com.jsjdesigns.poker.bot.TableTalkLine?,
|
tableTalk: com.jsjdesigns.poker.bot.TableTalkLine?,
|
||||||
handSummary: HandSummary?,
|
handSummary: HandSummary?,
|
||||||
|
seatStyle: SeatStyle,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val seats = snapshot?.seats.orEmpty()
|
val seats = snapshot?.seats.orEmpty()
|
||||||
@@ -313,7 +389,7 @@ private fun PokerTable(
|
|||||||
|
|
||||||
// Seat width and position both come from the measured table, so five
|
// Seat width and position both come from the measured table, so five
|
||||||
// seats fit with real gaps on a 384dp phone as well as a 427dp emulator.
|
// seats fit with real gaps on a 384dp phone as well as a 427dp emulator.
|
||||||
val seatWidth = opponentSeatWidthDp(maxWidth.value)
|
val seatWidth = opponentSeatWidthDp(maxWidth.value, seatStyle)
|
||||||
val placements = opponentSeatPlacements(maxWidth.value, seatWidth)
|
val placements = opponentSeatPlacements(maxWidth.value, seatWidth)
|
||||||
seats.filter { it.index != HERO_SEAT }
|
seats.filter { it.index != HERO_SEAT }
|
||||||
.forEachIndexed { index, seat ->
|
.forEachIndexed { index, seat ->
|
||||||
@@ -323,6 +399,7 @@ private fun PokerTable(
|
|||||||
isTurn = snapshot?.toAct == seat.index,
|
isTurn = snapshot?.toAct == seat.index,
|
||||||
isWinner = seat.index in winners,
|
isWinner = seat.index in winners,
|
||||||
width = seatWidth.dp,
|
width = seatWidth.dp,
|
||||||
|
style = seatStyle,
|
||||||
modifier = Modifier.offset(
|
modifier = Modifier.offset(
|
||||||
x = placement.leftDp.dp,
|
x = placement.leftDp.dp,
|
||||||
// Placement gives where the avatar centre belongs on the
|
// Placement gives where the avatar centre belongs on the
|
||||||
@@ -424,6 +501,35 @@ private fun TableFelt(modifier: Modifier = Modifier) {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun OpponentSeat(
|
private fun OpponentSeat(
|
||||||
|
seat: SeatSnapshot,
|
||||||
|
isTurn: Boolean,
|
||||||
|
isWinner: Boolean,
|
||||||
|
width: Dp,
|
||||||
|
style: SeatStyle,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
when (style) {
|
||||||
|
SeatStyle.PLATE -> PlateSeat(seat, isTurn, isWinner, width, modifier)
|
||||||
|
SeatStyle.BADGE -> BadgeSeat(seat, isTurn, isWinner, width, modifier)
|
||||||
|
SeatStyle.CHIP -> ChipSeat(seat, isTurn, isWinner, width, modifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun seatHighlight(isTurn: Boolean, isWinner: Boolean): Color = when {
|
||||||
|
isWinner -> TableGold
|
||||||
|
isTurn -> GoldLight
|
||||||
|
else -> Color.White.copy(alpha = 0.12f)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun seatStackLabel(seat: SeatSnapshot): String = when {
|
||||||
|
seat.folded -> "folded"
|
||||||
|
seat.allIn -> "ALL IN"
|
||||||
|
else -> "${seat.stack}"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun PlateSeat(
|
||||||
seat: SeatSnapshot,
|
seat: SeatSnapshot,
|
||||||
isTurn: Boolean,
|
isTurn: Boolean,
|
||||||
isWinner: Boolean,
|
isWinner: Boolean,
|
||||||
@@ -444,11 +550,7 @@ private fun OpponentSeat(
|
|||||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
) {
|
) {
|
||||||
Box(contentAlignment = Alignment.Center) {
|
Box(contentAlignment = Alignment.Center) {
|
||||||
val highlight = when {
|
val highlight = seatHighlight(isTurn, isWinner)
|
||||||
isWinner -> TableGold
|
|
||||||
isTurn -> GoldLight
|
|
||||||
else -> Color.White.copy(alpha = 0.12f)
|
|
||||||
}
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.border(
|
.border(
|
||||||
@@ -538,6 +640,152 @@ private fun OpponentSeat(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Badge: one horizontal pill, avatar beside a compact name and stack.
|
||||||
|
*
|
||||||
|
* Trades vertical height for width. The seat occupies about one row instead of
|
||||||
|
* three, which leaves more open felt between the arc and the pot.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun BadgeSeat(
|
||||||
|
seat: SeatSnapshot,
|
||||||
|
isTurn: Boolean,
|
||||||
|
isWinner: Boolean,
|
||||||
|
width: Dp,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val faded = seat.folded && !isWinner
|
||||||
|
val avatar = (width * 0.42f).coerceIn(30.dp, 42.dp)
|
||||||
|
Column(
|
||||||
|
modifier = modifier.width(width).alpha(if (faded) 0.4f else 1f),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(3.dp),
|
||||||
|
) {
|
||||||
|
Box(contentAlignment = Alignment.CenterStart) {
|
||||||
|
if (!seat.folded && !(seat.revealed && seat.hole != null)) {
|
||||||
|
HoleCardFan(
|
||||||
|
cardWidth = (width * 0.21f).coerceIn(16.dp, 24.dp),
|
||||||
|
modifier = Modifier.align(Alignment.CenterEnd).offset(x = 4.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Surface(
|
||||||
|
shape = RoundedCornerShape(50),
|
||||||
|
color = Panel,
|
||||||
|
border = androidx.compose.foundation.BorderStroke(
|
||||||
|
if (isTurn || isWinner) 2.dp else 1.dp,
|
||||||
|
seatHighlight(isTurn, isWinner),
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(end = 10.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||||
|
) {
|
||||||
|
OpponentAvatar(seat, size = avatar)
|
||||||
|
Column(modifier = Modifier.weight(1f, fill = false)) {
|
||||||
|
Text(
|
||||||
|
seat.name,
|
||||||
|
color = Cream,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 10.sp,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
seatStackLabel(seat),
|
||||||
|
color = if (seat.folded) Muted.copy(alpha = 0.7f) else TableGold,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 11.sp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (seat.isButton) {
|
||||||
|
DealerButton(Modifier.align(Alignment.BottomStart).offset(y = 6.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (seat.revealed && seat.hole != null) {
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(3.dp)) {
|
||||||
|
repeat(2) { i ->
|
||||||
|
CardImage(
|
||||||
|
seat.hole?.getOrNull(i),
|
||||||
|
Modifier.size((width * 0.34f).coerceIn(26.dp, 40.dp), (width * 0.51f).coerceIn(39.dp, 60.dp)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (seat.committedThisRound > 0 && !seat.folded) BetMarker(seat.committedThisRound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chip: avatar-led with the name and stack tucked beneath.
|
||||||
|
*
|
||||||
|
* The narrowest option, which buys the most separation between neighbours on a
|
||||||
|
* small screen at the cost of showing less at a glance.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun ChipSeat(
|
||||||
|
seat: SeatSnapshot,
|
||||||
|
isTurn: Boolean,
|
||||||
|
isWinner: Boolean,
|
||||||
|
width: Dp,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val faded = seat.folded && !isWinner
|
||||||
|
Column(
|
||||||
|
modifier = modifier.width(width).alpha(if (faded) 0.4f else 1f),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
|
) {
|
||||||
|
Box(contentAlignment = Alignment.Center) {
|
||||||
|
if (!seat.folded && !(seat.revealed && seat.hole != null)) {
|
||||||
|
HoleCardFan(
|
||||||
|
cardWidth = (width * 0.30f).coerceIn(16.dp, 22.dp),
|
||||||
|
modifier = Modifier.align(Alignment.TopEnd).offset(x = 10.dp, y = (-6).dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.border(
|
||||||
|
width = if (isTurn || isWinner) 2.dp else 1.dp,
|
||||||
|
color = seatHighlight(isTurn, isWinner),
|
||||||
|
shape = CircleShape,
|
||||||
|
).padding(2.dp),
|
||||||
|
) {
|
||||||
|
OpponentAvatar(seat, size = (width * 0.78f).coerceIn(34.dp, 46.dp))
|
||||||
|
}
|
||||||
|
if (seat.isButton) {
|
||||||
|
DealerButton(Modifier.align(Alignment.BottomStart).offset(x = (-6).dp, y = 4.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (seat.revealed && seat.hole != null) {
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||||
|
repeat(2) { i ->
|
||||||
|
CardImage(
|
||||||
|
seat.hole?.getOrNull(i),
|
||||||
|
Modifier.size((width * 0.44f).coerceIn(22.dp, 32.dp), (width * 0.66f).coerceIn(33.dp, 48.dp)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
seat.name,
|
||||||
|
color = Cream,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 10.sp,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
seatStackLabel(seat),
|
||||||
|
color = if (seat.folded) Muted.copy(alpha = 0.7f) else TableGold,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 11.sp,
|
||||||
|
)
|
||||||
|
if (seat.committedThisRound > 0 && !seat.folded) BetMarker(seat.committedThisRound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Two face-down cards fanned like a real hand.
|
* Two face-down cards fanned like a real hand.
|
||||||
*
|
*
|
||||||
@@ -870,7 +1118,7 @@ private fun TableControls(
|
|||||||
heroFolded: Boolean,
|
heroFolded: Boolean,
|
||||||
onFold: (Long) -> Unit,
|
onFold: (Long) -> Unit,
|
||||||
onCheckCall: (Long) -> Unit,
|
onCheckCall: (Long) -> Unit,
|
||||||
onRaise: (Long, Int) -> Unit,
|
onOpenRaise: () -> Unit,
|
||||||
onNextHand: (Int) -> Unit,
|
onNextHand: (Int) -> Unit,
|
||||||
) {
|
) {
|
||||||
Surface(
|
Surface(
|
||||||
@@ -878,7 +1126,7 @@ private fun TableControls(
|
|||||||
color = Night,
|
color = Night,
|
||||||
) {
|
) {
|
||||||
when {
|
when {
|
||||||
offer != null -> DecisionControls(offer, onFold, onCheckCall, onRaise)
|
offer != null -> DecisionControls(offer, onFold, onCheckCall, onOpenRaise)
|
||||||
handSummary != null -> CompletionControls(
|
handSummary != null -> CompletionControls(
|
||||||
summary = handSummary,
|
summary = handSummary,
|
||||||
nextHandRequested = nextHandRequested,
|
nextHandRequested = nextHandRequested,
|
||||||
@@ -908,36 +1156,53 @@ private fun DecisionControls(
|
|||||||
offer: DecisionOffer,
|
offer: DecisionOffer,
|
||||||
onFold: (Long) -> Unit,
|
onFold: (Long) -> Unit,
|
||||||
onCheckCall: (Long) -> Unit,
|
onCheckCall: (Long) -> Unit,
|
||||||
onRaise: (Long, Int) -> Unit,
|
onOpenRaise: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val buttons = buttonsFor(offer)
|
val buttons = buttonsFor(offer)
|
||||||
var sizingRaise by remember(offer.token) { mutableStateOf(false) }
|
|
||||||
var raiseTo by remember(offer.token) { mutableIntStateOf(buttons.fixedRaiseTo) }
|
|
||||||
|
|
||||||
BackHandler(enabled = sizingRaise) {
|
|
||||||
sizingRaise = false
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.background(
|
|
||||||
Brush.verticalGradient(
|
|
||||||
listOf(Color.Transparent, Night.copy(alpha = 0.95f), Night),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.padding(horizontal = 14.dp, vertical = 10.dp),
|
.padding(horizontal = 14.dp, vertical = 10.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(CONTROL_GAP),
|
|
||||||
) {
|
) {
|
||||||
if (!sizingRaise) {
|
|
||||||
PrimaryDecisionButtons(
|
PrimaryDecisionButtons(
|
||||||
offer = offer,
|
offer = offer,
|
||||||
buttons = buttons,
|
buttons = buttons,
|
||||||
onFold = onFold,
|
onFold = onFold,
|
||||||
onCheckCall = onCheckCall,
|
onCheckCall = onCheckCall,
|
||||||
onOpenRaise = { sizingRaise = true },
|
onOpenRaise = onOpenRaise,
|
||||||
)
|
)
|
||||||
} else {
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raise sizing, presented as a sheet over the felt.
|
||||||
|
*
|
||||||
|
* It must not live in the control column: growing that column shrinks the
|
||||||
|
* weighted table above it, which visibly reshapes the oval every time Raise is
|
||||||
|
* tapped. Overlaying leaves the table untouched.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun RaiseSizingSheet(
|
||||||
|
offer: DecisionOffer,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onRaise: (Long, Int) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val buttons = buttonsFor(offer)
|
||||||
|
var raiseTo by remember(offer.token) { mutableIntStateOf(buttons.fixedRaiseTo) }
|
||||||
|
BackHandler(enabled = true) { onDismiss() }
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(
|
||||||
|
Brush.verticalGradient(
|
||||||
|
listOf(Color.Transparent, Night.copy(alpha = 0.97f), Night),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.padding(horizontal = 14.dp, vertical = 10.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(CONTROL_GAP),
|
||||||
|
) {
|
||||||
val amount = raiseConfirmationAmount(buttons, raiseTo)
|
val amount = raiseConfirmationAmount(buttons, raiseTo)
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -946,7 +1211,7 @@ private fun DecisionControls(
|
|||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
BackToActionsButton { sizingRaise = false }
|
BackToActionsButton { onDismiss() }
|
||||||
Column(horizontalAlignment = Alignment.End) {
|
Column(horizontalAlignment = Alignment.End) {
|
||||||
Text(
|
Text(
|
||||||
"RAISE TO",
|
"RAISE TO",
|
||||||
@@ -1060,7 +1325,6 @@ private fun DecisionControls(
|
|||||||
onClick = { onRaise(offer.token, amount) },
|
onClick = { onRaise(offer.token, amount) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -1448,9 +1712,10 @@ private fun CardImage(index: Int?, modifier: Modifier) {
|
|||||||
Image(
|
Image(
|
||||||
painter = painterResource(if (index != null) cardFace(index) else cardBack()),
|
painter = painterResource(if (index != null) cardFace(index) else cardBack()),
|
||||||
contentDescription = index?.let { Card(it).toString() } ?: "Face-down card",
|
contentDescription = index?.let { Card(it).toString() } ?: "Face-down card",
|
||||||
|
// No clip and no backing plate: the artwork is already a rounded card
|
||||||
|
// with its own border, drawn at the same 2:3 ratio it is laid out at.
|
||||||
|
// Clipping again at a different radius cut into that border.
|
||||||
contentScale = ContentScale.Fit,
|
contentScale = ContentScale.Fit,
|
||||||
modifier = modifier
|
modifier = modifier,
|
||||||
.clip(RoundedCornerShape(6.dp))
|
|
||||||
.background(Color(0xFFFFFDF8)),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user