feat(desktop): collapsible info panel in chess deck column

Hide GameInfo/MoveHistory/Actions panel by default in deck view
with a chevron toggle to expand. Add Chess to AddColumnDialog.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
nrobi144
2026-03-04 09:45:01 +02:00
parent 991ac3b31b
commit 73b8059b55
3 changed files with 198 additions and 172 deletions
@@ -39,6 +39,8 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Refresh
@@ -90,6 +92,7 @@ fun ChessScreen(
relayManager: DesktopRelayConnectionManager,
account: AccountState.LoggedIn,
onBack: () -> Unit = {},
compactMode: Boolean = false,
) {
val scope = rememberCoroutineScope()
val viewModel =
@@ -274,6 +277,7 @@ fun ChessScreen(
},
onResign = { viewModel.resign(gameState.startEventId) },
isSpectatorOverride = isSpectating,
compactMode = compactMode,
)
}
} else {
@@ -593,6 +597,7 @@ private fun DesktopChessGameLayout(
onMoveMade: (from: String, to: String, san: String) -> Unit,
onResign: () -> Unit,
isSpectatorOverride: Boolean? = null,
compactMode: Boolean = false,
) {
// Collect state flows to trigger recomposition on changes
val currentPosition by gameState.currentPosition.collectAsState()
@@ -604,12 +609,13 @@ private fun DesktopChessGameLayout(
val opponentPubkey = gameState.opponentPubkey
val isSpectator = isSpectatorOverride ?: gameState.isSpectator
var showInfoPanel by remember { mutableStateOf(!compactMode) }
BoxWithConstraints(
modifier = Modifier.fillMaxSize().padding(16.dp),
) {
// Calculate board size based on available space
// Leave room for the info panel (300dp + 24dp spacing)
val infoPanelWidth = 300.dp + 24.dp
val infoPanelWidth = if (showInfoPanel) 300.dp + 24.dp else 0.dp
val availableWidth = maxWidth - infoPanelWidth
val availableHeight = maxHeight
// Board should fit within available space, maintaining square aspect ratio
@@ -617,11 +623,11 @@ private fun DesktopChessGameLayout(
Row(
modifier = Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.spacedBy(24.dp),
horizontalArrangement = Arrangement.spacedBy(if (showInfoPanel) 24.dp else 0.dp),
) {
// Left side: Chess board
// Left side: Chess board + toggle
Box(
modifier = Modifier.fillMaxHeight(),
modifier = Modifier.weight(1f).fillMaxHeight(),
contentAlignment = Alignment.Center,
) {
InteractiveChessBoard(
@@ -633,9 +639,26 @@ private fun DesktopChessGameLayout(
positionVersion = moveHistory.size,
onMoveMade = onMoveMade,
)
// Toggle button anchored to top-end of board area
IconButton(
onClick = { showInfoPanel = !showInfoPanel },
modifier = Modifier.align(Alignment.TopEnd),
) {
Icon(
if (showInfoPanel) {
Icons.AutoMirrored.Filled.KeyboardArrowRight
} else {
Icons.AutoMirrored.Filled.KeyboardArrowLeft
},
contentDescription = if (showInfoPanel) "Hide info panel" else "Show info panel",
tint = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}
// Right side: Game info, moves, controls
// Right side: Game info, moves, controls (collapsible)
if (showInfoPanel) {
Column(
modifier =
Modifier
@@ -818,4 +841,5 @@ private fun DesktopChessGameLayout(
}
}
}
}
}
@@ -56,6 +56,7 @@ private val COLUMN_OPTIONS =
DeckColumnType.Bookmarks,
DeckColumnType.GlobalFeed,
DeckColumnType.MyProfile,
DeckColumnType.Chess,
)
@Composable
@@ -276,6 +276,7 @@ internal fun RootContent(
relayManager = relayManager,
account = account,
onBack = {},
compactMode = true,
)
}