From ed9d942ce086315a7654a05f330355d3c3d063f6 Mon Sep 17 00:00:00 2001 From: davotoula Date: Mon, 23 Mar 2026 16:24:28 +0100 Subject: [PATCH] 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 --- .../screen/loggedIn/chess/ChessLobbyScreen.kt | 53 ++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chess/ChessLobbyScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chess/ChessLobbyScreen.kt index 42f56380f..333d9c6e1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chess/ChessLobbyScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chess/ChessLobbyScreen.kt @@ -67,6 +67,7 @@ import com.vitorpamplona.amethyst.commons.chess.ActiveGameCard import com.vitorpamplona.amethyst.commons.chess.ChallengeCard import com.vitorpamplona.amethyst.commons.chess.ChessChallenge 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.OutgoingChallengeCard import com.vitorpamplona.amethyst.commons.chess.OverlappingAvatars @@ -295,13 +296,15 @@ fun ChessLobbyContent( val spectatingGames by chessViewModel.spectatingGames.collectAsState() val publicGames by chessViewModel.publicGames.collectAsState() val challenges by chessViewModel.challenges.collectAsState() + val completedGames by chessViewModel.completedGames.collectAsState() val userPubkey = accountViewModel.account.userProfile().pubkeyHex val hasContent = activeGames.isNotEmpty() || spectatingGames.isNotEmpty() || publicGames.isNotEmpty() || - challenges.isNotEmpty() + challenges.isNotEmpty() || + completedGames.isNotEmpty() if (!hasContent) { // 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 item { Spacer(Modifier.height(80.dp))