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:
+9
@@ -350,6 +350,15 @@ fun ChessGameScreen(
|
|||||||
onResign = { chessViewModel.resign(gameId) },
|
onResign = { chessViewModel.resign(gameId) },
|
||||||
isSpectatorOverride = isSpectating,
|
isSpectatorOverride = isSpectating,
|
||||||
onGameEndDismiss = { chessViewModel.dismissGame(gameId) },
|
onGameEndDismiss = { chessViewModel.dismissGame(gameId) },
|
||||||
|
onLeaveSpectating =
|
||||||
|
if (isSpectating) {
|
||||||
|
{
|
||||||
|
chessViewModel.stopSpectating(gameId)
|
||||||
|
nav.popBack()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
},
|
||||||
whiteName = whiteDisplayName,
|
whiteName = whiteDisplayName,
|
||||||
whiteHex = whitePubkey,
|
whiteHex = whitePubkey,
|
||||||
whiteAvatarUrl = whiteAvatarUrl,
|
whiteAvatarUrl = whiteAvatarUrl,
|
||||||
|
|||||||
+1
-1
@@ -176,7 +176,7 @@ class ChessViewModelNew(
|
|||||||
|
|
||||||
fun loadGameAsSpectator(gameId: String) = logic.loadGameAsSpectator(gameId)
|
fun loadGameAsSpectator(gameId: String) = logic.loadGameAsSpectator(gameId)
|
||||||
|
|
||||||
fun stopSpectating(gameId: String) = logic.state.removeSpectatingGame(gameId)
|
fun stopSpectating(gameId: String) = logic.stopSpectating(gameId)
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// Utility
|
// Utility
|
||||||
|
|||||||
+5
@@ -505,6 +505,11 @@ class ChessLobbyLogic(
|
|||||||
// Spectator mode
|
// Spectator mode
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|
||||||
|
fun stopSpectating(gameId: String) {
|
||||||
|
state.removeSpectatingGame(gameId)
|
||||||
|
pollingDelegate.removeGameId(gameId)
|
||||||
|
}
|
||||||
|
|
||||||
fun loadGameAsSpectator(startEventId: String) {
|
fun loadGameAsSpectator(startEventId: String) {
|
||||||
scope.launch(Dispatchers.Default) {
|
scope.launch(Dispatchers.Default) {
|
||||||
state.setBroadcastStatus(ChessBroadcastStatus.Syncing(0f))
|
state.setBroadcastStatus(ChessBroadcastStatus.Syncing(0f))
|
||||||
|
|||||||
+28
-16
@@ -201,6 +201,7 @@ fun LiveChessGameScreen(
|
|||||||
onResign: () -> Unit,
|
onResign: () -> Unit,
|
||||||
isSpectatorOverride: Boolean? = null,
|
isSpectatorOverride: Boolean? = null,
|
||||||
onGameEndDismiss: (() -> Unit)? = null,
|
onGameEndDismiss: (() -> Unit)? = null,
|
||||||
|
onLeaveSpectating: (() -> Unit)? = null,
|
||||||
whiteName: String = "White",
|
whiteName: String = "White",
|
||||||
whiteHex: String = "",
|
whiteHex: String = "",
|
||||||
whiteAvatarUrl: String? = null,
|
whiteAvatarUrl: String? = null,
|
||||||
@@ -299,7 +300,7 @@ fun LiveChessGameScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
isSpectator -> {
|
isSpectator -> {
|
||||||
SpectatorInfo()
|
SpectatorInfo(onLeaveSpectating = onLeaveSpectating)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
@@ -547,22 +548,33 @@ private fun GameInfoHeader(
|
|||||||
* Info banner shown when spectating a game
|
* Info banner shown when spectating a game
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
private fun SpectatorInfo() {
|
private fun SpectatorInfo(onLeaveSpectating: (() -> Unit)? = null) {
|
||||||
Box(
|
Column(
|
||||||
modifier =
|
modifier = Modifier.fillMaxWidth(),
|
||||||
Modifier
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
.fillMaxWidth()
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
.background(
|
|
||||||
MaterialTheme.colorScheme.tertiaryContainer.copy(alpha = 0.5f),
|
|
||||||
RoundedCornerShape(8.dp),
|
|
||||||
).padding(12.dp),
|
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
) {
|
) {
|
||||||
Text(
|
Box(
|
||||||
text = "Watching game - spectator mode",
|
modifier =
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
Modifier
|
||||||
color = MaterialTheme.colorScheme.onTertiaryContainer,
|
.fillMaxWidth()
|
||||||
)
|
.background(
|
||||||
|
MaterialTheme.colorScheme.tertiaryContainer.copy(alpha = 0.5f),
|
||||||
|
RoundedCornerShape(8.dp),
|
||||||
|
).padding(12.dp),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Watching game - spectator mode",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onTertiaryContainer,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onLeaveSpectating?.let { onLeave ->
|
||||||
|
OutlinedButton(onClick = onLeave) {
|
||||||
|
Text("Leave Game")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+30
-8
@@ -281,6 +281,15 @@ fun ChessScreen(
|
|||||||
},
|
},
|
||||||
onResign = { viewModel.resign(gameState.startEventId) },
|
onResign = { viewModel.resign(gameState.startEventId) },
|
||||||
isSpectatorOverride = isSpectating,
|
isSpectatorOverride = isSpectating,
|
||||||
|
onLeaveSpectating =
|
||||||
|
if (isSpectating) {
|
||||||
|
{
|
||||||
|
viewModel.stopSpectating(gameState.startEventId)
|
||||||
|
viewModel.selectGame(null)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
},
|
||||||
compactMode = compactMode,
|
compactMode = compactMode,
|
||||||
whiteName = viewModel.userMetadataCache.getDisplayName(whitePubkey),
|
whiteName = viewModel.userMetadataCache.getDisplayName(whitePubkey),
|
||||||
whiteHex = whitePubkey,
|
whiteHex = whitePubkey,
|
||||||
@@ -614,6 +623,7 @@ private fun DesktopChessGameLayout(
|
|||||||
onMoveMade: (from: String, to: String, san: String) -> Unit,
|
onMoveMade: (from: String, to: String, san: String) -> Unit,
|
||||||
onResign: () -> Unit,
|
onResign: () -> Unit,
|
||||||
isSpectatorOverride: Boolean? = null,
|
isSpectatorOverride: Boolean? = null,
|
||||||
|
onLeaveSpectating: (() -> Unit)? = null,
|
||||||
compactMode: Boolean = false,
|
compactMode: Boolean = false,
|
||||||
whiteName: String = "White",
|
whiteName: String = "White",
|
||||||
whiteHex: String = "",
|
whiteHex: String = "",
|
||||||
@@ -860,16 +870,28 @@ private fun DesktopChessGameLayout(
|
|||||||
containerColor = MaterialTheme.colorScheme.tertiaryContainer.copy(alpha = 0.5f),
|
containerColor = MaterialTheme.colorScheme.tertiaryContainer.copy(alpha = 0.5f),
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
Row(
|
Column(
|
||||||
modifier = Modifier.padding(16.dp),
|
modifier = Modifier.padding(16.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Default.Visibility, contentDescription = null)
|
Row(
|
||||||
Text(
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
"Watching game - spectator mode",
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
) {
|
||||||
)
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -172,7 +172,7 @@ class DesktopChessViewModelNew(
|
|||||||
|
|
||||||
fun loadGameAsSpectator(gameId: String) = logic.loadGameAsSpectator(gameId)
|
fun loadGameAsSpectator(gameId: String) = logic.loadGameAsSpectator(gameId)
|
||||||
|
|
||||||
fun stopSpectating(gameId: String) = logic.state.removeSpectatingGame(gameId)
|
fun stopSpectating(gameId: String) = logic.stopSpectating(gameId)
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// Utility
|
// Utility
|
||||||
|
|||||||
Reference in New Issue
Block a user