fix(chess): filter own games from live list, dismissable errors, clickable avatars and completed games

- Filter user's own games from public "Live Games" list in lobby
- Fix broken addSpectatingGame filter (playerPubkey is always viewerPubkey)
- Add removeGame() to clean up stale entries when "Game not found"
- Make player avatars clickable to open profile on game screen
- Make completed game cards clickable to view final board state

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
davotoula
2026-03-23 18:01:29 +01:00
parent 08d5f2ebb5
commit c86daa6950
10 changed files with 47 additions and 4 deletions
@@ -288,6 +288,7 @@ fun CompletedGameCard(
didUserWin: Boolean,
isDraw: Boolean,
moveCount: Int,
onClick: (() -> Unit)? = null,
modifier: Modifier = Modifier,
avatar: @Composable (() -> Unit)? = null,
) {
@@ -309,6 +310,8 @@ fun CompletedGameCard(
}
Card(
onClick = onClick ?: {},
enabled = onClick != null,
modifier = modifier.fillMaxWidth(),
) {
Row(
@@ -856,6 +856,16 @@ class ChessLobbyLogic(
}
}
/**
* Remove a game from all lobby lists (active, spectating) and stop polling.
* Used when a game fails to load ("Game not found") so the user can dismiss the stale entry.
*/
fun removeGame(gameId: String) {
state.removeActiveGame(gameId)
state.removeSpectatingGame(gameId)
pollingDelegate.removeGameId(gameId)
}
fun clearError() {
state.setError(null)
}
@@ -297,7 +297,8 @@ class ChessLobbyState(
}
fun updatePublicGames(games: List<PublicGame>) {
_publicGames.value = games
// Filter out games where the user is a participant
_publicGames.value = games.filter { it.whitePubkey != userPubkey && it.blackPubkey != userPubkey }
}
fun addActiveGame(
@@ -501,7 +502,9 @@ class ChessLobbyState(
return
}
// Don't add own games to spectating list
if (state.playerPubkey == userPubkey || state.opponentPubkey == userPubkey) {
// Note: LiveChessGameState.playerPubkey is always viewerPubkey regardless of role,
// so we check isSpectator instead to determine if the user is actually a participant
if (!state.isSpectator) {
return
}
// Don't add finished games to spectating list
@@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.commons.chess
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -64,6 +65,7 @@ fun ChessPlayerChip(
avatarUrl: String?,
isActive: Boolean = false,
mirrored: Boolean = false,
onClick: (() -> Unit)? = null,
modifier: Modifier = Modifier,
) {
val avatar =
@@ -102,7 +104,10 @@ fun ChessPlayerChip(
}
Row(
modifier = modifier.alpha(if (isActive) 1f else 0.6f),
modifier =
modifier
.alpha(if (isActive) 1f else 0.6f)
.then(if (onClick != null) Modifier.clickable(onClick = onClick) else Modifier),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(6.dp),
) {
@@ -146,6 +151,8 @@ fun ChessPlayerVsHeader(
blackHex: String,
blackAvatarUrl: String?,
isWhiteTurn: Boolean,
onWhiteClick: (() -> Unit)? = null,
onBlackClick: (() -> Unit)? = null,
modifier: Modifier = Modifier,
) {
Row(
@@ -163,6 +170,7 @@ fun ChessPlayerVsHeader(
userHex = whiteHex,
avatarUrl = whiteAvatarUrl,
isActive = isWhiteTurn,
onClick = onWhiteClick,
)
Text(
text = "White",
@@ -188,6 +196,7 @@ fun ChessPlayerVsHeader(
avatarUrl = blackAvatarUrl,
isActive = !isWhiteTurn,
mirrored = true,
onClick = onBlackClick,
)
Text(
text = "Black",
@@ -208,6 +208,7 @@ fun LiveChessGameScreen(
blackName: String = "Black",
blackHex: String = "",
blackAvatarUrl: String? = null,
onPlayerClick: ((pubkeyHex: String) -> Unit)? = null,
) {
// Observe state flows for automatic recomposition on updates
val currentPosition by gameState.currentPosition.collectAsState()
@@ -253,6 +254,8 @@ fun LiveChessGameScreen(
blackHex = blackHex,
blackAvatarUrl = blackAvatarUrl,
isWhiteTurn = currentPosition.activeColor == Color.WHITE,
onWhiteClick = onPlayerClick?.let { { it(whiteHex) } },
onBlackClick = onPlayerClick?.let { { it(blackHex) } },
modifier = Modifier.fillMaxWidth(),
)
}