fix: address Copilot review comments
- ChessPosition: include all fields in equals/hashCode - AcceptedGamesRegistry: add thread safety with synchronized - ChessEventBroadcaster: use valid filter for connection trigger - ChessEventBroadcaster: fix misleading relayResults - Remove println debug logging from ChessSubscription/Broadcaster - UserProfileScreen: use proper JSON parsing via MetadataEvent Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+1
@@ -89,6 +89,7 @@ class FeedTopNavFilterState(
|
|||||||
CHESS -> {
|
CHESS -> {
|
||||||
ChessFeedFlow(followsRelays, proxyRelays)
|
ChessFeedFlow(followsRelays, proxyRelays)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
val note = LocalCache.checkGetOrCreateAddressableNote(listName)
|
val note = LocalCache.checkGetOrCreateAddressableNote(listName)
|
||||||
if (note != null) {
|
if (note != null) {
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
@@ -925,6 +925,7 @@ private fun RenderNoteRow(
|
|||||||
nav,
|
nav,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is LiveChessGameChallengeEvent -> {
|
is LiveChessGameChallengeEvent -> {
|
||||||
RenderLiveChessChallenge(
|
RenderLiveChessChallenge(
|
||||||
baseNote,
|
baseNote,
|
||||||
@@ -933,6 +934,7 @@ private fun RenderNoteRow(
|
|||||||
nav,
|
nav,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is LiveChessGameEndEvent -> {
|
is LiveChessGameEndEvent -> {
|
||||||
RenderLiveChessGameEnd(
|
RenderLiveChessGameEnd(
|
||||||
baseNote,
|
baseNote,
|
||||||
@@ -941,6 +943,7 @@ private fun RenderNoteRow(
|
|||||||
nav,
|
nav,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is ClassifiedsEvent -> {
|
is ClassifiedsEvent -> {
|
||||||
RenderClassifieds(
|
RenderClassifieds(
|
||||||
noteEvent,
|
noteEvent,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
@@ -107,8 +107,12 @@ fun RenderLiveChessChallenge(
|
|||||||
|
|
||||||
val borderColor =
|
val borderColor =
|
||||||
when {
|
when {
|
||||||
isOpenChallenge -> Color(0xFF4CAF50) // Green for open
|
isOpenChallenge -> Color(0xFF4CAF50)
|
||||||
isIncomingChallenge -> Color(0xFFFFA726) // Orange for incoming
|
|
||||||
|
// Green for open
|
||||||
|
isIncomingChallenge -> Color(0xFFFFA726)
|
||||||
|
|
||||||
|
// Orange for incoming
|
||||||
else -> MaterialTheme.colorScheme.outline // Gray for sent
|
else -> MaterialTheme.colorScheme.outline // Gray for sent
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +195,7 @@ fun RenderLiveChessChallenge(
|
|||||||
gameId = gameId,
|
gameId = gameId,
|
||||||
challengerPubkey = challengerPubkey,
|
challengerPubkey = challengerPubkey,
|
||||||
challengerDisplayName = note.author?.toBestDisplayName(),
|
challengerDisplayName = note.author?.toBestDisplayName(),
|
||||||
challengerAvatarUrl = note.author?.info?.profilePicture(),
|
challengerAvatarUrl = note.author?.profilePicture(),
|
||||||
opponentPubkey = event.opponentPubkey(),
|
opponentPubkey = event.opponentPubkey(),
|
||||||
challengerColor = event.playerColor() ?: ChessColor.WHITE,
|
challengerColor = event.playerColor() ?: ChessColor.WHITE,
|
||||||
createdAt = event.createdAt,
|
createdAt = event.createdAt,
|
||||||
|
|||||||
+3
-4
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
@@ -398,12 +398,11 @@ class AndroidRelayFetcher(
|
|||||||
class AndroidMetadataProvider : IUserMetadataProvider {
|
class AndroidMetadataProvider : IUserMetadataProvider {
|
||||||
override fun getDisplayName(pubkey: String): String {
|
override fun getDisplayName(pubkey: String): String {
|
||||||
val user = LocalCache.getOrCreateUser(pubkey)
|
val user = LocalCache.getOrCreateUser(pubkey)
|
||||||
return user.info?.bestName()
|
return user.toBestDisplayName()
|
||||||
?: user.pubkeyDisplayHex()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPictureUrl(pubkey: String): String? {
|
override fun getPictureUrl(pubkey: String): String? {
|
||||||
val user = LocalCache.getOrCreateUser(pubkey)
|
val user = LocalCache.getOrCreateUser(pubkey)
|
||||||
return user.info?.profilePicture()
|
return user.profilePicture()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
@@ -213,9 +213,11 @@ fun ChessGameScreen(
|
|||||||
is ChessBroadcastStatus.Failed -> {
|
is ChessBroadcastStatus.Failed -> {
|
||||||
// Could implement retry logic here
|
// Could implement retry logic here
|
||||||
}
|
}
|
||||||
|
|
||||||
is ChessBroadcastStatus.Desynced -> {
|
is ChessBroadcastStatus.Desynced -> {
|
||||||
chessViewModel.forceRefresh()
|
chessViewModel.forceRefresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> { }
|
else -> { }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -240,6 +242,7 @@ fun ChessGameScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gameState == null -> {
|
gameState == null -> {
|
||||||
// Game not found - show error with back button
|
// Game not found - show error with back button
|
||||||
Column(
|
Column(
|
||||||
@@ -286,6 +289,7 @@ fun ChessGameScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
// Resolve opponent display name
|
// Resolve opponent display name
|
||||||
val opponentDisplayName =
|
val opponentDisplayName =
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+2
@@ -250,9 +250,11 @@ fun HomeScreenFloatingButton(
|
|||||||
is LocationState.LocationResult.Loading -> { }
|
is LocationState.LocationResult.Loading -> { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CHESS -> {
|
CHESS -> {
|
||||||
NewChessGameButton(accountViewModel, nav)
|
NewChessGameButton(accountViewModel, nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
NewNoteButton(nav)
|
NewNoteButton(nav)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+15
-5
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
@@ -29,19 +29,29 @@ package com.vitorpamplona.amethyst.commons.chess
|
|||||||
*/
|
*/
|
||||||
object AcceptedGamesRegistry {
|
object AcceptedGamesRegistry {
|
||||||
private val acceptedGameIds = mutableSetOf<String>()
|
private val acceptedGameIds = mutableSetOf<String>()
|
||||||
|
private val lock = Any()
|
||||||
|
|
||||||
fun markAsAccepted(gameId: String) {
|
fun markAsAccepted(gameId: String) {
|
||||||
acceptedGameIds.add(gameId)
|
synchronized(lock) {
|
||||||
|
acceptedGameIds.add(gameId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun wasAccepted(gameId: String): Boolean = acceptedGameIds.contains(gameId)
|
fun wasAccepted(gameId: String): Boolean =
|
||||||
|
synchronized(lock) {
|
||||||
|
acceptedGameIds.contains(gameId)
|
||||||
|
}
|
||||||
|
|
||||||
fun clear() {
|
fun clear() {
|
||||||
acceptedGameIds.clear()
|
synchronized(lock) {
|
||||||
|
acceptedGameIds.clear()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Remove old entries - call periodically to prevent memory leak */
|
/** Remove old entries - call periodically to prevent memory leak */
|
||||||
fun clearOldEntries(keepGameIds: Set<String>) {
|
fun clearOldEntries(keepGameIds: Set<String>) {
|
||||||
acceptedGameIds.retainAll(keepGameIds)
|
synchronized(lock) {
|
||||||
|
acceptedGameIds.retainAll(keepGameIds)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+25
-10
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
@@ -149,12 +149,17 @@ fun ChessBroadcastBanner(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun getStatusBackgroundColor(status: ChessBroadcastStatus): Color =
|
private fun getStatusBackgroundColor(status: ChessBroadcastStatus): Color =
|
||||||
when (status) {
|
when (status) {
|
||||||
is ChessBroadcastStatus.Failed, is ChessBroadcastStatus.Desynced ->
|
is ChessBroadcastStatus.Failed, is ChessBroadcastStatus.Desynced -> {
|
||||||
MaterialTheme.colorScheme.errorContainer
|
MaterialTheme.colorScheme.errorContainer
|
||||||
is ChessBroadcastStatus.Success ->
|
}
|
||||||
|
|
||||||
|
is ChessBroadcastStatus.Success -> {
|
||||||
MaterialTheme.colorScheme.primaryContainer
|
MaterialTheme.colorScheme.primaryContainer
|
||||||
else ->
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
MaterialTheme.colorScheme.surfaceContainer
|
MaterialTheme.colorScheme.surfaceContainer
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getStatusIcon(status: ChessBroadcastStatus): ImageVector =
|
private fun getStatusIcon(status: ChessBroadcastStatus): ImageVector =
|
||||||
@@ -171,14 +176,21 @@ private fun getStatusIcon(status: ChessBroadcastStatus): ImageVector =
|
|||||||
@Composable
|
@Composable
|
||||||
private fun getStatusIconColor(status: ChessBroadcastStatus): Color =
|
private fun getStatusIconColor(status: ChessBroadcastStatus): Color =
|
||||||
when (status) {
|
when (status) {
|
||||||
is ChessBroadcastStatus.Failed, is ChessBroadcastStatus.Desynced ->
|
is ChessBroadcastStatus.Failed, is ChessBroadcastStatus.Desynced -> {
|
||||||
MaterialTheme.colorScheme.error
|
MaterialTheme.colorScheme.error
|
||||||
is ChessBroadcastStatus.Success ->
|
}
|
||||||
|
|
||||||
|
is ChessBroadcastStatus.Success -> {
|
||||||
MaterialTheme.colorScheme.primary
|
MaterialTheme.colorScheme.primary
|
||||||
is ChessBroadcastStatus.WaitingForOpponent ->
|
}
|
||||||
|
|
||||||
|
is ChessBroadcastStatus.WaitingForOpponent -> {
|
||||||
MaterialTheme.colorScheme.secondary
|
MaterialTheme.colorScheme.secondary
|
||||||
else ->
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
MaterialTheme.colorScheme.primary
|
MaterialTheme.colorScheme.primary
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getStatusText(status: ChessBroadcastStatus): String =
|
private fun getStatusText(status: ChessBroadcastStatus): String =
|
||||||
@@ -205,10 +217,13 @@ private fun getStatusDetail(status: ChessBroadcastStatus): String =
|
|||||||
@Composable
|
@Composable
|
||||||
private fun getStatusDetailColor(status: ChessBroadcastStatus): Color =
|
private fun getStatusDetailColor(status: ChessBroadcastStatus): Color =
|
||||||
when (status) {
|
when (status) {
|
||||||
is ChessBroadcastStatus.Failed, is ChessBroadcastStatus.Desynced ->
|
is ChessBroadcastStatus.Failed, is ChessBroadcastStatus.Desynced -> {
|
||||||
MaterialTheme.colorScheme.error
|
MaterialTheme.colorScheme.error
|
||||||
else ->
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
MaterialTheme.colorScheme.primary
|
MaterialTheme.colorScheme.primary
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getStatusProgress(status: ChessBroadcastStatus): Float? =
|
private fun getStatusProgress(status: ChessBroadcastStatus): Float? =
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+5
-2
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
@@ -126,7 +126,10 @@ object ChessGameLoader {
|
|||||||
LoadGameResult.Error("Failed to convert reconstructed state to live state")
|
LoadGameResult.Error("Failed to convert reconstructed state to live state")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is ReconstructionResult.Error -> LoadGameResult.Error(result.message)
|
|
||||||
|
is ReconstructionResult.Error -> {
|
||||||
|
LoadGameResult.Error(result.message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+5
-2
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
@@ -301,7 +301,10 @@ fun CompletedGameCard(
|
|||||||
val resultColor =
|
val resultColor =
|
||||||
when {
|
when {
|
||||||
isDraw -> MaterialTheme.colorScheme.onSurfaceVariant
|
isDraw -> MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
didUserWin -> Color(0xFF4CAF50) // Green
|
|
||||||
|
didUserWin -> Color(0xFF4CAF50)
|
||||||
|
|
||||||
|
// Green
|
||||||
else -> Color(0xFFF44336) // Red
|
else -> Color(0xFFF44336) // Red
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
@@ -389,6 +389,7 @@ class ChessLobbyLogic(
|
|||||||
state.setBroadcastStatus(ChessBroadcastStatus.Idle)
|
state.setBroadcastStatus(ChessBroadcastStatus.Idle)
|
||||||
state.setError(null)
|
state.setError(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
is LoadGameResult.Error -> {
|
is LoadGameResult.Error -> {
|
||||||
state.setError("Failed to load game: ${result.message}")
|
state.setError("Failed to load game: ${result.message}")
|
||||||
state.setBroadcastStatus(ChessBroadcastStatus.Idle)
|
state.setBroadcastStatus(ChessBroadcastStatus.Idle)
|
||||||
@@ -518,6 +519,7 @@ class ChessLobbyLogic(
|
|||||||
// Auto-select the game for Desktop (Android uses route navigation)
|
// Auto-select the game for Desktop (Android uses route navigation)
|
||||||
state.selectGame(startEventId)
|
state.selectGame(startEventId)
|
||||||
}
|
}
|
||||||
|
|
||||||
is LoadGameResult.Error -> {
|
is LoadGameResult.Error -> {
|
||||||
state.setError("Failed to load game: ${result.message}")
|
state.setError("Failed to load game: ${result.message}")
|
||||||
state.setBroadcastStatus(ChessBroadcastStatus.Idle)
|
state.setBroadcastStatus(ChessBroadcastStatus.Idle)
|
||||||
@@ -549,6 +551,7 @@ class ChessLobbyLogic(
|
|||||||
state.setBroadcastStatus(ChessBroadcastStatus.Idle)
|
state.setBroadcastStatus(ChessBroadcastStatus.Idle)
|
||||||
state.setError(null)
|
state.setError(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
is LoadGameResult.Error -> {
|
is LoadGameResult.Error -> {
|
||||||
// Check again if game was added while we were fetching
|
// Check again if game was added while we were fetching
|
||||||
// (e.g., by acceptChallenge completing in parallel)
|
// (e.g., by acceptChallenge completing in parallel)
|
||||||
@@ -582,6 +585,7 @@ class ChessLobbyLogic(
|
|||||||
is LoadGameResult.Success -> {
|
is LoadGameResult.Success -> {
|
||||||
state.replaceGameState(startEventId, result.liveState)
|
state.replaceGameState(startEventId, result.liveState)
|
||||||
}
|
}
|
||||||
|
|
||||||
is LoadGameResult.Error -> {
|
is LoadGameResult.Error -> {
|
||||||
// Don't overwrite error for periodic refresh failures
|
// Don't overwrite error for periodic refresh failures
|
||||||
}
|
}
|
||||||
@@ -754,6 +758,7 @@ class ChessLobbyLogic(
|
|||||||
pollingDelegate.addGameId(startEventId)
|
pollingDelegate.addGameId(startEventId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is LoadGameResult.Error -> {
|
is LoadGameResult.Error -> {
|
||||||
// Failed to load game - continue with others
|
// Failed to load game - continue with others
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+67
-19
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
@@ -266,15 +266,25 @@ fun LiveChessGameScreen(
|
|||||||
|
|
||||||
// Show appropriate controls based on game state
|
// Show appropriate controls based on game state
|
||||||
when {
|
when {
|
||||||
gameStatus is GameStatus.Finished ->
|
gameStatus is GameStatus.Finished -> {
|
||||||
GameEndInfo(
|
GameEndInfo(
|
||||||
result = (gameStatus as GameStatus.Finished).result,
|
result = (gameStatus as GameStatus.Finished).result,
|
||||||
playerColor = gameState.playerColor,
|
playerColor = gameState.playerColor,
|
||||||
isSpectator = isSpectator,
|
isSpectator = isSpectator,
|
||||||
)
|
)
|
||||||
gameState.isPendingChallenge -> PendingChallengeInfo()
|
}
|
||||||
isSpectator -> SpectatorInfo()
|
|
||||||
else -> GameControls(onResign = onResign)
|
gameState.isPendingChallenge -> {
|
||||||
|
PendingChallengeInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
isSpectator -> {
|
||||||
|
SpectatorInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
GameControls(onResign = onResign)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -422,6 +432,7 @@ private fun GameInfoHeader(
|
|||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
isSpectator -> {
|
isSpectator -> {
|
||||||
Text(
|
Text(
|
||||||
text = "Spectating",
|
text = "Spectating",
|
||||||
@@ -437,6 +448,7 @@ private fun GameInfoHeader(
|
|||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
Text(
|
Text(
|
||||||
text = "vs $opponentName",
|
text = "vs $opponentName",
|
||||||
@@ -456,17 +468,26 @@ private fun GameInfoHeader(
|
|||||||
val resultText =
|
val resultText =
|
||||||
when {
|
when {
|
||||||
result == GameResult.DRAW -> "Draw"
|
result == GameResult.DRAW -> "Draw"
|
||||||
|
|
||||||
(result == GameResult.WHITE_WINS && playerColor == Color.WHITE) ||
|
(result == GameResult.WHITE_WINS && playerColor == Color.WHITE) ||
|
||||||
(result == GameResult.BLACK_WINS && playerColor == Color.BLACK) -> "You won!"
|
(result == GameResult.BLACK_WINS && playerColor == Color.BLACK) -> "You won!"
|
||||||
|
|
||||||
else -> "You lost"
|
else -> "You lost"
|
||||||
}
|
}
|
||||||
val resultColor =
|
val resultColor =
|
||||||
when {
|
when {
|
||||||
result == GameResult.DRAW -> MaterialTheme.colorScheme.secondary
|
result == GameResult.DRAW -> {
|
||||||
|
MaterialTheme.colorScheme.secondary
|
||||||
|
}
|
||||||
|
|
||||||
(result == GameResult.WHITE_WINS && playerColor == Color.WHITE) ||
|
(result == GameResult.WHITE_WINS && playerColor == Color.WHITE) ||
|
||||||
(result == GameResult.BLACK_WINS && playerColor == Color.BLACK) ->
|
(result == GameResult.BLACK_WINS && playerColor == Color.BLACK) -> {
|
||||||
ComposeColor(0xFF4CAF50)
|
ComposeColor(0xFF4CAF50)
|
||||||
else -> MaterialTheme.colorScheme.error
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
MaterialTheme.colorScheme.error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
text = resultText,
|
text = resultText,
|
||||||
@@ -475,6 +496,7 @@ private fun GameInfoHeader(
|
|||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
val turnText =
|
val turnText =
|
||||||
if (currentTurn == playerColor) {
|
if (currentTurn == playerColor) {
|
||||||
@@ -685,27 +707,33 @@ private fun GameEndOverlay(
|
|||||||
}
|
}
|
||||||
Quadruple("", "Game Over", winnerText, MaterialTheme.colorScheme.surfaceVariant)
|
Quadruple("", "Game Over", winnerText, MaterialTheme.colorScheme.surfaceVariant)
|
||||||
}
|
}
|
||||||
playerWon ->
|
|
||||||
|
playerWon -> {
|
||||||
Quadruple(
|
Quadruple(
|
||||||
"",
|
"",
|
||||||
"Victory!",
|
"Victory!",
|
||||||
"Congratulations!",
|
"Congratulations!",
|
||||||
ComposeColor(0xFF4CAF50).copy(alpha = 0.95f),
|
ComposeColor(0xFF4CAF50).copy(alpha = 0.95f),
|
||||||
)
|
)
|
||||||
isDraw ->
|
}
|
||||||
|
|
||||||
|
isDraw -> {
|
||||||
Quadruple(
|
Quadruple(
|
||||||
"",
|
"",
|
||||||
"Draw",
|
"Draw",
|
||||||
"Game ended in a draw",
|
"Game ended in a draw",
|
||||||
MaterialTheme.colorScheme.surfaceVariant,
|
MaterialTheme.colorScheme.surfaceVariant,
|
||||||
)
|
)
|
||||||
else ->
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
Quadruple(
|
Quadruple(
|
||||||
"",
|
"",
|
||||||
"Defeat",
|
"Defeat",
|
||||||
"Better luck next time!",
|
"Better luck next time!",
|
||||||
ComposeColor(0xFFE57373).copy(alpha = 0.95f),
|
ComposeColor(0xFFE57373).copy(alpha = 0.95f),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Animated visibility
|
// Animated visibility
|
||||||
@@ -828,27 +856,47 @@ private fun GameEndInfo(
|
|||||||
) {
|
) {
|
||||||
val resultText =
|
val resultText =
|
||||||
when {
|
when {
|
||||||
isSpectator ->
|
isSpectator -> {
|
||||||
when (result) {
|
when (result) {
|
||||||
GameResult.WHITE_WINS -> "White wins"
|
GameResult.WHITE_WINS -> "White wins"
|
||||||
GameResult.BLACK_WINS -> "Black wins"
|
GameResult.BLACK_WINS -> "Black wins"
|
||||||
GameResult.DRAW -> "Draw"
|
GameResult.DRAW -> "Draw"
|
||||||
GameResult.IN_PROGRESS -> "In progress"
|
GameResult.IN_PROGRESS -> "In progress"
|
||||||
}
|
}
|
||||||
result == GameResult.DRAW -> "Game drawn"
|
}
|
||||||
|
|
||||||
|
result == GameResult.DRAW -> {
|
||||||
|
"Game drawn"
|
||||||
|
}
|
||||||
|
|
||||||
(result == GameResult.WHITE_WINS && playerColor == Color.WHITE) ||
|
(result == GameResult.WHITE_WINS && playerColor == Color.WHITE) ||
|
||||||
(result == GameResult.BLACK_WINS && playerColor == Color.BLACK) -> "You won!"
|
(result == GameResult.BLACK_WINS && playerColor == Color.BLACK) -> {
|
||||||
else -> "You lost"
|
"You won!"
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
"You lost"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val backgroundColor =
|
val backgroundColor =
|
||||||
when {
|
when {
|
||||||
isSpectator -> MaterialTheme.colorScheme.surfaceVariant
|
isSpectator -> {
|
||||||
result == GameResult.DRAW -> MaterialTheme.colorScheme.secondaryContainer
|
MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
}
|
||||||
|
|
||||||
|
result == GameResult.DRAW -> {
|
||||||
|
MaterialTheme.colorScheme.secondaryContainer
|
||||||
|
}
|
||||||
|
|
||||||
(result == GameResult.WHITE_WINS && playerColor == Color.WHITE) ||
|
(result == GameResult.WHITE_WINS && playerColor == Color.WHITE) ||
|
||||||
(result == GameResult.BLACK_WINS && playerColor == Color.BLACK) ->
|
(result == GameResult.BLACK_WINS && playerColor == Color.BLACK) -> {
|
||||||
ComposeColor(0xFF4CAF50).copy(alpha = 0.3f)
|
ComposeColor(0xFF4CAF50).copy(alpha = 0.3f)
|
||||||
else -> MaterialTheme.colorScheme.errorContainer.copy(alpha = 0.5f)
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
MaterialTheme.colorScheme.errorContainer.copy(alpha = 0.5f)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+10
-16
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
@@ -67,21 +67,19 @@ class ChessEventBroadcaster(
|
|||||||
val targetRelays = ChessConfig.CHESS_RELAYS.map { NormalizedRelayUrl(it) }.toSet()
|
val targetRelays = ChessConfig.CHESS_RELAYS.map { NormalizedRelayUrl(it) }.toSet()
|
||||||
val subId = newSubId()
|
val subId = newSubId()
|
||||||
|
|
||||||
println("[ChessEventBroadcaster] Broadcasting event ${event.id.take(8)} to ${targetRelays.size} relays")
|
|
||||||
|
|
||||||
// Step 1: Check which relays are already connected
|
// Step 1: Check which relays are already connected
|
||||||
val initialConnected = client.connectedRelaysFlow().value
|
val initialConnected = client.connectedRelaysFlow().value
|
||||||
val alreadyConnected = targetRelays.intersect(initialConnected)
|
val alreadyConnected = targetRelays.intersect(initialConnected)
|
||||||
val needsConnection = targetRelays - alreadyConnected
|
val needsConnection = targetRelays - alreadyConnected
|
||||||
|
|
||||||
println("[ChessEventBroadcaster] Already connected: ${alreadyConnected.size}, needs connection: ${needsConnection.size}")
|
|
||||||
|
|
||||||
// Step 2: If some relays need connection, open a subscription to trigger it
|
// Step 2: If some relays need connection, open a subscription to trigger it
|
||||||
if (needsConnection.isNotEmpty()) {
|
if (needsConnection.isNotEmpty()) {
|
||||||
|
// Use a valid filter with recent since timestamp to trigger connection
|
||||||
|
// without expecting real results
|
||||||
val dummyFilter =
|
val dummyFilter =
|
||||||
Filter(
|
Filter(
|
||||||
kinds = listOf(JesterProtocol.KIND),
|
kinds = listOf(JesterProtocol.KIND),
|
||||||
ids = listOf("trigger_connection_${System.currentTimeMillis()}"),
|
since = (System.currentTimeMillis() / 1000) + 3600, // 1 hour in future = no results
|
||||||
limit = 1,
|
limit = 1,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -97,9 +95,7 @@ class ChessEventBroadcaster(
|
|||||||
override fun onEose(
|
override fun onEose(
|
||||||
relay: NormalizedRelayUrl,
|
relay: NormalizedRelayUrl,
|
||||||
forFilters: List<Filter>?,
|
forFilters: List<Filter>?,
|
||||||
) {
|
) { }
|
||||||
println("[ChessEventBroadcaster] EOSE from ${relay.url}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open subscription to all target relays (triggers connection)
|
// Open subscription to all target relays (triggers connection)
|
||||||
@@ -107,23 +103,21 @@ class ChessEventBroadcaster(
|
|||||||
client.openReqSubscription(subId, filterMap, listener)
|
client.openReqSubscription(subId, filterMap, listener)
|
||||||
|
|
||||||
// Wait for relays to connect (poll with timeout)
|
// Wait for relays to connect (poll with timeout)
|
||||||
val connected = waitForRelays(targetRelays, 5000L)
|
waitForRelays(targetRelays, 5000L)
|
||||||
println("[ChessEventBroadcaster] After waiting: ${connected.size}/${targetRelays.size} connected")
|
|
||||||
|
|
||||||
// Close the dummy subscription
|
// Close the dummy subscription
|
||||||
client.close(subId)
|
client.close(subId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 3: Send the event and wait for OK responses
|
// Step 3: Send the event and wait for OK responses
|
||||||
println("[ChessEventBroadcaster] Sending event with sendAndWaitForResponse...")
|
|
||||||
val success = client.sendAndWaitForResponse(event, targetRelays, timeoutSeconds)
|
val success = client.sendAndWaitForResponse(event, targetRelays, timeoutSeconds)
|
||||||
|
|
||||||
println("[ChessEventBroadcaster] Broadcast complete: success=$success")
|
// Note: sendAndWaitForResponse only returns aggregate success (any relay accepted)
|
||||||
|
// We don't have per-relay results, so relayResults is empty
|
||||||
return BroadcastResult(
|
return BroadcastResult(
|
||||||
success = success,
|
success = success,
|
||||||
relayResults = targetRelays.associateWith { success },
|
relayResults = emptyMap(),
|
||||||
message = if (success) "Event accepted by relay(s)" else "No relay accepted the event",
|
message = if (success) "Event accepted by at least one relay" else "No relay accepted the event",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+2
-5
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
@@ -104,10 +104,7 @@ class DesktopChessSubscriptionController(
|
|||||||
) {
|
) {
|
||||||
// Add chess events to local cache for persistence
|
// Add chess events to local cache for persistence
|
||||||
if (event.kind in CHESS_EVENT_KINDS) {
|
if (event.kind in CHESS_EVENT_KINDS) {
|
||||||
val isNew = DesktopChessEventCache.add(event)
|
DesktopChessEventCache.add(event)
|
||||||
if (isNew) {
|
|
||||||
println("[ChessSubscription] Cached new event: kind=${event.kind}, id=${event.id.take(8)}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
onEvent(event, isLive, relay, forFilters)
|
onEvent(event, isLive, relay, forFilters)
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-24
@@ -201,21 +201,25 @@ fun UserProfileScreen(
|
|||||||
relays = configuredRelays,
|
relays = configuredRelays,
|
||||||
pubKeyHex = pubKeyHex,
|
pubKeyHex = pubKeyHex,
|
||||||
onEvent = { event, _, _, _ ->
|
onEvent = { event, _, _, _ ->
|
||||||
try {
|
if (event is MetadataEvent) {
|
||||||
val content = event.content
|
try {
|
||||||
displayName = extractJsonField(content, "display_name") ?: extractJsonField(content, "name")
|
val metadata = event.contactMetaData()
|
||||||
about = extractJsonField(content, "about")
|
if (metadata != null) {
|
||||||
picture = extractJsonField(content, "picture")
|
displayName = metadata.displayName ?: metadata.name
|
||||||
|
about = metadata.about
|
||||||
// Store MetadataEvent for editing (only for own profile)
|
picture = metadata.picture
|
||||||
if (isOwnProfile && event is MetadataEvent) {
|
|
||||||
val current = latestMetadataEvent
|
|
||||||
if (current == null || event.createdAt > current.createdAt) {
|
|
||||||
latestMetadataEvent = event
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store MetadataEvent for editing (only for own profile)
|
||||||
|
if (isOwnProfile) {
|
||||||
|
val current = latestMetadataEvent
|
||||||
|
if (current == null || event.createdAt > current.createdAt) {
|
||||||
|
latestMetadataEvent = event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Ignore parse errors
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
// Ignore parse errors
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -688,17 +692,6 @@ fun UserProfileScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple JSON field extractor (not production-ready, just for demo).
|
|
||||||
*/
|
|
||||||
private fun extractJsonField(
|
|
||||||
json: String,
|
|
||||||
field: String,
|
|
||||||
): String? {
|
|
||||||
val regex = """"$field"\s*:\s*"([^"]*)"""".toRegex()
|
|
||||||
return regex.find(json)?.groupValues?.get(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Follows a user by publishing an updated contact list event.
|
* Follows a user by publishing an updated contact list event.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
@@ -177,13 +177,19 @@ data class ChessPosition(
|
|||||||
if (other !is ChessPosition) return false
|
if (other !is ChessPosition) return false
|
||||||
return board.contentDeepEquals(other.board) &&
|
return board.contentDeepEquals(other.board) &&
|
||||||
activeColor == other.activeColor &&
|
activeColor == other.activeColor &&
|
||||||
moveNumber == other.moveNumber
|
moveNumber == other.moveNumber &&
|
||||||
|
castlingRights == other.castlingRights &&
|
||||||
|
enPassantSquare == other.enPassantSquare &&
|
||||||
|
halfMoveClock == other.halfMoveClock
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
var result = board.contentDeepHashCode()
|
var result = board.contentDeepHashCode()
|
||||||
result = 31 * result + activeColor.hashCode()
|
result = 31 * result + activeColor.hashCode()
|
||||||
result = 31 * result + moveNumber
|
result = 31 * result + moveNumber
|
||||||
|
result = 31 * result + castlingRights.hashCode()
|
||||||
|
result = 31 * result + (enPassantSquare?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + halfMoveClock
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-3
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
@@ -155,14 +155,21 @@ object ChessStateReconstructor {
|
|||||||
val result = parseGameResult(gameResult)
|
val result = parseGameResult(gameResult)
|
||||||
GameStatus.Finished(result)
|
GameStatus.Finished(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
engine.isCheckmate() -> {
|
engine.isCheckmate() -> {
|
||||||
val winner = engine.getSideToMove().opposite()
|
val winner = engine.getSideToMove().opposite()
|
||||||
GameStatus.Finished(
|
GameStatus.Finished(
|
||||||
if (winner == Color.WHITE) GameResult.WHITE_WINS else GameResult.BLACK_WINS,
|
if (winner == Color.WHITE) GameResult.WHITE_WINS else GameResult.BLACK_WINS,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
engine.isStalemate() -> GameStatus.Finished(GameResult.DRAW)
|
|
||||||
else -> GameStatus.InProgress
|
engine.isStalemate() -> {
|
||||||
|
GameStatus.Finished(GameResult.DRAW)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
GameStatus.InProgress
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the head event ID (for linking next move)
|
// Get the head event ID (for linking next move)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+2
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
@@ -464,6 +464,7 @@ class LiveChessGameState(
|
|||||||
_gameStatus.value = GameStatus.Finished(GameResult.getResultForWinner(winner))
|
_gameStatus.value = GameStatus.Finished(GameResult.getResultForWinner(winner))
|
||||||
// In a real implementation, you'd publish GameEnd event here
|
// In a real implementation, you'd publish GameEnd event here
|
||||||
}
|
}
|
||||||
|
|
||||||
engine.isStalemate() -> {
|
engine.isStalemate() -> {
|
||||||
_gameStatus.value = GameStatus.Finished(GameResult.DRAW)
|
_gameStatus.value = GameStatus.Finished(GameResult.DRAW)
|
||||||
// In a real implementation, you'd publish GameEnd event here
|
// In a real implementation, you'd publish GameEnd event here
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
@@ -258,11 +258,13 @@ object PGNParser {
|
|||||||
val kingSide = move.toSquare.contains("g")
|
val kingSide = move.toSquare.contains("g")
|
||||||
current.makeCastlingMove(kingSide)
|
current.makeCastlingMove(kingSide)
|
||||||
}
|
}
|
||||||
|
|
||||||
move.fromSquare != null -> {
|
move.fromSquare != null -> {
|
||||||
// Move with disambiguation (we can infer full source)
|
// Move with disambiguation (we can infer full source)
|
||||||
// For now, just use simplified move
|
// For now, just use simplified move
|
||||||
makeSimplifiedMove(current, move)
|
makeSimplifiedMove(current, move)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
// Regular move
|
// Regular move
|
||||||
makeSimplifiedMove(current, move)
|
makeSimplifiedMove(current, move)
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
+9
-3
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
@@ -231,8 +231,14 @@ actual class ChessEngine {
|
|||||||
val sameFile = ambiguous.any { it.from.file == fromSquare.file }
|
val sameFile = ambiguous.any { it.from.file == fromSquare.file }
|
||||||
val sameRank = ambiguous.any { it.from.rank == fromSquare.rank }
|
val sameRank = ambiguous.any { it.from.rank == fromSquare.rank }
|
||||||
when {
|
when {
|
||||||
!sameFile -> sb.append(fileChar(fromSquare))
|
!sameFile -> {
|
||||||
!sameRank -> sb.append(rankChar(fromSquare))
|
sb.append(fileChar(fromSquare))
|
||||||
|
}
|
||||||
|
|
||||||
|
!sameRank -> {
|
||||||
|
sb.append(rankChar(fromSquare))
|
||||||
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
sb.append(fileChar(fromSquare))
|
sb.append(fileChar(fromSquare))
|
||||||
sb.append(rankChar(fromSquare))
|
sb.append(rankChar(fromSquare))
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/*
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
|||||||
Reference in New Issue
Block a user