feat(chess): finished games section below active games in lobby

Adds a "Finished Games" section to the Android chess lobby screen,
showing up to 10 completed games with opponent avatars, result
(Won/Lost/Draw), and move count. The section only appears when
completedGames is non-empty, matching the Desktop implementation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
davotoula
2026-03-23 16:24:28 +01:00
parent 012e97494e
commit ed9d942ce0
@@ -67,6 +67,7 @@ import com.vitorpamplona.amethyst.commons.chess.ActiveGameCard
import com.vitorpamplona.amethyst.commons.chess.ChallengeCard import com.vitorpamplona.amethyst.commons.chess.ChallengeCard
import com.vitorpamplona.amethyst.commons.chess.ChessChallenge import com.vitorpamplona.amethyst.commons.chess.ChessChallenge
import com.vitorpamplona.amethyst.commons.chess.ChessSyncBanner import com.vitorpamplona.amethyst.commons.chess.ChessSyncBanner
import com.vitorpamplona.amethyst.commons.chess.CompletedGameCard
import com.vitorpamplona.amethyst.commons.chess.NewChessGameDialog import com.vitorpamplona.amethyst.commons.chess.NewChessGameDialog
import com.vitorpamplona.amethyst.commons.chess.OutgoingChallengeCard import com.vitorpamplona.amethyst.commons.chess.OutgoingChallengeCard
import com.vitorpamplona.amethyst.commons.chess.OverlappingAvatars import com.vitorpamplona.amethyst.commons.chess.OverlappingAvatars
@@ -295,13 +296,15 @@ fun ChessLobbyContent(
val spectatingGames by chessViewModel.spectatingGames.collectAsState() val spectatingGames by chessViewModel.spectatingGames.collectAsState()
val publicGames by chessViewModel.publicGames.collectAsState() val publicGames by chessViewModel.publicGames.collectAsState()
val challenges by chessViewModel.challenges.collectAsState() val challenges by chessViewModel.challenges.collectAsState()
val completedGames by chessViewModel.completedGames.collectAsState()
val userPubkey = accountViewModel.account.userProfile().pubkeyHex val userPubkey = accountViewModel.account.userProfile().pubkeyHex
val hasContent = val hasContent =
activeGames.isNotEmpty() || activeGames.isNotEmpty() ||
spectatingGames.isNotEmpty() || spectatingGames.isNotEmpty() ||
publicGames.isNotEmpty() || publicGames.isNotEmpty() ||
challenges.isNotEmpty() challenges.isNotEmpty() ||
completedGames.isNotEmpty()
if (!hasContent) { if (!hasContent) {
// Empty state - use LazyColumn so pull-to-refresh works // Empty state - use LazyColumn so pull-to-refresh works
@@ -560,6 +563,54 @@ fun ChessLobbyContent(
} }
} }
// Finished games section
if (completedGames.isNotEmpty()) {
item {
Spacer(Modifier.height(16.dp))
Text(
"Finished Games",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(vertical = 8.dp),
)
}
items(
completedGames.distinctBy { it.gameId }.take(10),
key = { "completed-${it.gameId}-${it.completedAt}" },
) { game ->
val opponentPubkey =
if (game.whitePubkey == userPubkey) game.blackPubkey else game.whitePubkey
val opponentUser =
remember(opponentPubkey) {
accountViewModel.checkGetOrCreateUser(opponentPubkey)
}
val opponentName =
opponentUser?.toBestDisplayName()
?: game.blackDisplayName
?: game.whiteDisplayName
?: opponentPubkey.take(8)
CompletedGameCard(
opponentName = opponentName,
result = game.result,
didUserWin = game.didUserWin(userPubkey),
isDraw = game.isDraw,
moveCount = game.moveCount,
avatar = {
OverlappingAvatars(
avatar1Hex = userPubkey,
avatar2Hex = opponentPubkey,
avatar1Url =
remember(userPubkey) {
accountViewModel.checkGetOrCreateUser(userPubkey)
}?.profilePicture(),
avatar2Url = opponentUser?.profilePicture(),
)
},
)
}
}
// Bottom padding for FAB // Bottom padding for FAB
item { item {
Spacer(Modifier.height(80.dp)) Spacer(Modifier.height(80.dp))