feat(chess): exit spectator mode with Leave Game button

Adds ability to leave a spectated game on both Android and Desktop:
- Added stopSpectating() to ChessLobbyLogic (removes from state + stops polling)
- Both ViewModels now delegate to logic.stopSpectating() instead of bypassing it
- LiveChessGameScreen accepts onLeaveSpectating callback shown in spectator info area
- Android: Leave Game button pops back stack and stops spectating
- Desktop: Leave Game button clears selectedGameId (returns to lobby) and stops spectating

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
davotoula
2026-03-23 16:28:44 +01:00
parent ed9d942ce0
commit 292c6de08a
6 changed files with 74 additions and 26 deletions
@@ -281,6 +281,15 @@ fun ChessScreen(
},
onResign = { viewModel.resign(gameState.startEventId) },
isSpectatorOverride = isSpectating,
onLeaveSpectating =
if (isSpectating) {
{
viewModel.stopSpectating(gameState.startEventId)
viewModel.selectGame(null)
}
} else {
null
},
compactMode = compactMode,
whiteName = viewModel.userMetadataCache.getDisplayName(whitePubkey),
whiteHex = whitePubkey,
@@ -614,6 +623,7 @@ private fun DesktopChessGameLayout(
onMoveMade: (from: String, to: String, san: String) -> Unit,
onResign: () -> Unit,
isSpectatorOverride: Boolean? = null,
onLeaveSpectating: (() -> Unit)? = null,
compactMode: Boolean = false,
whiteName: String = "White",
whiteHex: String = "",
@@ -860,16 +870,28 @@ private fun DesktopChessGameLayout(
containerColor = MaterialTheme.colorScheme.tertiaryContainer.copy(alpha = 0.5f),
),
) {
Row(
Column(
modifier = Modifier.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Icon(Icons.Default.Visibility, contentDescription = null)
Text(
"Watching game - spectator mode",
style = MaterialTheme.typography.bodyMedium,
)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
Icon(Icons.Default.Visibility, contentDescription = null)
Text(
"Watching game - spectator mode",
style = MaterialTheme.typography.bodyMedium,
)
}
onLeaveSpectating?.let { onLeave ->
OutlinedButton(
onClick = onLeave,
modifier = Modifier.fillMaxWidth(),
) {
Text("Leave Game")
}
}
}
}
}
@@ -172,7 +172,7 @@ class DesktopChessViewModelNew(
fun loadGameAsSpectator(gameId: String) = logic.loadGameAsSpectator(gameId)
fun stopSpectating(gameId: String) = logic.state.removeSpectatingGame(gameId)
fun stopSpectating(gameId: String) = logic.stopSpectating(gameId)
// ============================================
// Utility