First chess refactoring

This commit is contained in:
Vitor Pamplona
2026-03-02 18:14:08 -05:00
parent df72a6e094
commit 934b1fd5ed
9 changed files with 232 additions and 165 deletions
@@ -31,7 +31,7 @@ import com.vitorpamplona.amethyst.service.relayClient.searchCommand.SearchFilter
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.datasource.ChatroomFilterAssembler
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource.ChannelFilterAssembler
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.datasource.ChatroomListFilterAssembler
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chess.ChessFilterAssembler
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chess.datasource.ChessFilterAssembler
import com.vitorpamplona.amethyst.ui.screen.loggedIn.communities.datasource.CommunityFilterAssembler
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.datasource.DiscoveryFilterAssembler
import com.vitorpamplona.amethyst.ui.screen.loggedIn.followPacks.feed.datasource.FollowPackFeedFilterAssembler
@@ -71,6 +71,7 @@ import com.vitorpamplona.amethyst.commons.chess.ChessSyncBanner
import com.vitorpamplona.amethyst.commons.chess.LiveChessGameScreen
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chess.datasource.ChessSubscription
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.Height4dpModifier
import com.vitorpamplona.quartz.nip64Chess.ChessGameNameGenerator
@@ -128,7 +129,7 @@ fun ChessGameScreen(
// Subscribe to chess events when game screen is visible
// This is critical - without it, no new events will arrive from relays
ChessSubscription(accountViewModel, chessViewModel)
ChessSubscription(chessViewModel, accountViewModel)
// Handle initial game loading
// Once game is in maps, gameState above will always have the latest
@@ -37,7 +37,6 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Settings
@@ -53,7 +52,6 @@ import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
@@ -62,7 +60,6 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -72,20 +69,23 @@ import androidx.compose.ui.unit.dp
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.chess.ActiveGameCard
import com.vitorpamplona.amethyst.commons.chess.ChallengeCard
import com.vitorpamplona.amethyst.commons.chess.ChessBroadcastBanner
import com.vitorpamplona.amethyst.commons.chess.ChessChallenge
import com.vitorpamplona.amethyst.commons.chess.ChessConfig
import com.vitorpamplona.amethyst.commons.chess.ChessSyncBanner
import com.vitorpamplona.amethyst.commons.chess.NewChessGameDialog
import com.vitorpamplona.amethyst.commons.chess.PublicGame
import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chess.datasource.ChessSubscription
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl
import com.vitorpamplona.quartz.nip64Chess.LiveChessGameState
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import com.vitorpamplona.quartz.nip64Chess.Color as ChessColor
/**
@@ -106,47 +106,14 @@ fun ChessLobbyScreen(
factory = ChessViewModelFactory(accountViewModel.account),
)
val activeGames by chessViewModel.activeGames.collectAsState()
val spectatingGames by chessViewModel.spectatingGames.collectAsState()
val publicGames by chessViewModel.publicGames.collectAsState()
val challenges by chessViewModel.challenges.collectAsState()
val error by chessViewModel.error.collectAsState()
val broadcastStatus by chessViewModel.broadcastStatus.collectAsState()
val syncStatus by chessViewModel.syncStatus.collectAsState()
val selectedGameId by chessViewModel.selectedGameId.collectAsState()
val userPubkey = accountViewModel.account.userProfile().pubkeyHex
var showNewGameDialog by remember { mutableStateOf(false) }
var showRelaySettings by remember { mutableStateOf(false) }
var isRefreshing by remember { mutableStateOf(false) }
val scope = rememberCoroutineScope()
// Get relay information for settings display
val inboxRelays by accountViewModel.account.notificationRelays.flow
.collectAsState()
val outboxRelays by accountViewModel.account.outboxRelays.flow
.collectAsState()
val globalRelays by accountViewModel.account.defaultGlobalRelays.flow
.collectAsState()
// Subscribe to chess events when screen is visible
ChessSubscription(accountViewModel, chessViewModel)
ChessSubscription(chessViewModel, accountViewModel)
// Clear any stale game selection on mount
LaunchedEffect(Unit) {
chessViewModel.selectGame(null)
}
// Auto-navigate when a game is selected (e.g., after accepting a challenge)
LaunchedEffect(selectedGameId, activeGames) {
selectedGameId?.let { gameId ->
if (activeGames.containsKey(gameId)) {
nav.nav(Route.ChessGame(gameId))
chessViewModel.selectGame(null)
}
}
}
// Set lobby mode (poll all games) when screen is visible
// Don't stop polling in onDispose - ViewModel lifecycle handles that
// This prevents the race where lobby's onDispose fires AFTER game screen enters
@@ -161,6 +128,42 @@ fun ChessLobbyScreen(
}
}
ChessLobbyScreen(chessViewModel, accountViewModel, nav)
}
/**
* Chess lobby screen showing challenges and active games
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ChessLobbyScreen(
chessViewModel: ChessViewModelNew,
accountViewModel: AccountViewModel,
nav: INav,
) {
val activeGames by chessViewModel.activeGames.collectAsState()
val spectatingGames by chessViewModel.spectatingGames.collectAsState()
val publicGames by chessViewModel.publicGames.collectAsState()
val challenges by chessViewModel.challenges.collectAsState()
val error by chessViewModel.error.collectAsState()
val broadcastStatus by chessViewModel.broadcastStatus.collectAsState()
val syncStatus by chessViewModel.syncStatus.collectAsState()
val selectedGameId by chessViewModel.selectedGameId.collectAsState()
val userPubkey = accountViewModel.account.userProfile().pubkeyHex
var showNewGameDialog by remember { mutableStateOf(false) }
var showRelaySettings by remember { mutableStateOf(false) }
// Auto-navigate when a game is selected (e.g., after accepting a challenge)
LaunchedEffect(selectedGameId, activeGames) {
selectedGameId?.let { gameId ->
if (activeGames.containsKey(gameId)) {
nav.nav(Route.ChessGame(gameId))
chessViewModel.selectGame(null)
}
}
}
Scaffold(
topBar = {
TopAppBar(
@@ -187,25 +190,14 @@ fun ChessLobbyScreen(
NewChessGameButton { showNewGameDialog = true }
},
) { paddingValues ->
PullToRefreshBox(
isRefreshing = isRefreshing,
onRefresh = {
scope.launch {
isRefreshing = true
chessViewModel.forceRefresh()
delay(500) // Brief delay for visual feedback
isRefreshing = false
}
},
modifier =
Modifier
.fillMaxSize()
.padding(paddingValues),
RefresheableBox(
onRefresh = { chessViewModel.forceRefresh() },
) {
Column(
modifier =
Modifier
.fillMaxSize()
.padding(paddingValues)
.padding(horizontal = 16.dp),
) {
// Sync status banner (shows relay progress during loading)
@@ -285,11 +277,9 @@ fun ChessLobbyScreen(
sheetState = rememberModalBottomSheetState(),
) {
ChessRelaySettingsSheet(
inboxRelays = inboxRelays.map { it.displayUrl() },
outboxRelays = outboxRelays.map { it.displayUrl() },
globalRelays = globalRelays.map { it.displayUrl() },
challengeCount = challenges.size,
publicGameCount = publicGames.size,
accountViewModel = accountViewModel,
)
}
}
@@ -373,7 +363,7 @@ fun ChessLobbyContent(
remember(state.opponentPubkey) {
accountViewModel.checkGetOrCreateUser(state.opponentPubkey)?.toBestDisplayName() ?: state.opponentPubkey.take(8)
}
com.vitorpamplona.amethyst.commons.chess.ActiveGameCard(
ActiveGameCard(
gameId = gameId,
opponentName = displayName,
isYourTurn = state.isPlayerTurn(),
@@ -452,7 +442,7 @@ fun ChessLobbyContent(
?: accountViewModel.checkGetOrCreateUser(challenge.challengerPubkey)?.toBestDisplayName()
?: challenge.challengerPubkey.take(8)
}
com.vitorpamplona.amethyst.commons.chess.ChallengeCard(
ChallengeCard(
challengerName = displayName,
challengerPlaysWhite = challenge.challengerColor == ChessColor.WHITE,
isIncoming = true,
@@ -526,13 +516,19 @@ fun ChessLobbyContent(
*/
@Composable
private fun ChessRelaySettingsSheet(
inboxRelays: List<String>,
outboxRelays: List<String>,
globalRelays: List<String>,
challengeCount: Int,
publicGameCount: Int,
connectedRelayCount: Int = 0,
accountViewModel: AccountViewModel,
) {
// Get relay information for settings display
val inboxRelays by accountViewModel.account.notificationRelays.flow
.collectAsState()
val outboxRelays by accountViewModel.account.outboxRelays.flow
.collectAsState()
val globalRelays by accountViewModel.account.defaultGlobalRelays.flow
.collectAsState()
Column(
modifier =
Modifier
@@ -601,10 +597,10 @@ private fun ChessRelaySettingsSheet(
// Show chess relays and their connection status
ChessConfig.CHESS_RELAY_NAMES.forEach { relay ->
val isConnected =
inboxRelays.any { it.contains(relay) } ||
outboxRelays.any { it.contains(relay) } ||
globalRelays.any { it.contains(relay) }
ChessRelayRow(relayUrl = "wss://$relay/", isConnected = isConnected)
inboxRelays.contains(relay) ||
outboxRelays.contains(relay) ||
globalRelays.contains(relay)
ChessRelayRow(relay = relay, isConnected = isConnected)
}
Spacer(modifier = Modifier.height(16.dp))
@@ -714,7 +710,7 @@ private fun ChessRelaySettingsSheet(
@Composable
private fun RelayRow(
relayUrl: String,
relayUrl: NormalizedRelayUrl,
isPreferred: Boolean = false,
) {
Row(
@@ -737,7 +733,7 @@ private fun RelayRow(
modifier = Modifier.size(12.dp),
)
Text(
text = relayUrl.removePrefix("wss://").removePrefix("ws://"),
text = relayUrl.displayUrl(),
style = MaterialTheme.typography.bodySmall,
fontWeight = if (isPreferred) FontWeight.Medium else FontWeight.Normal,
)
@@ -753,7 +749,7 @@ private fun RelayRow(
@Composable
private fun ChessRelayRow(
relayUrl: String,
relay: NormalizedRelayUrl,
isConnected: Boolean,
) {
Row(
@@ -781,7 +777,7 @@ private fun ChessRelayRow(
modifier = Modifier.size(16.dp),
)
Text(
text = relayUrl.removePrefix("wss://").removePrefix("ws://").removeSuffix("/"),
text = relay.displayUrl(),
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Medium,
)
@@ -20,6 +20,7 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chess
import androidx.compose.runtime.Stable
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.vitorpamplona.amethyst.commons.chess.ChessBroadcastStatus
@@ -46,16 +47,13 @@ import kotlinx.coroutines.flow.StateFlow
* - Platform adapter creation
* - State exposure to Compose UI
*/
@Stable
class ChessViewModelNew(
private val account: Account,
) : ViewModel() {
// Instance ID for debugging ViewModel sharing
val instanceId = System.identityHashCode(this)
init {
println("[ChessViewModelNew] Created instance $instanceId for ${account.userProfile().pubkeyHex.take(8)}")
}
// Platform adapters
private val publisher = AndroidChessPublisher(account)
private val fetcher = AndroidRelayFetcher(account)
@@ -0,0 +1,54 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chess.datasource
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
/**
* Query state for chess subscription
*/
data class ChessQueryState(
val userPubkey: String,
val inboxRelays: Set<NormalizedRelayUrl>,
val globalRelays: Set<NormalizedRelayUrl>,
val isGlobal: Boolean,
val activeGameIds: Set<String> = emptySet(),
val opponentPubkeys: Set<String> = emptySet(),
)
/**
* Sub-assembler that creates the actual relay filters
*/
class ChessFeedFilterSubAssembler(
client: INostrClient,
allKeys: () -> Set<ChessQueryState>,
) : PerUniqueIdEoseManager<ChessQueryState, String>(client, allKeys) {
override fun updateFilter(
key: ChessQueryState,
since: SincePerRelayMap?,
): List<RelayBasedFilter> = filterChessEvents(key, since)
override fun id(key: ChessQueryState): String = key.userPubkey
}
@@ -0,0 +1,42 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chess.datasource
import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
/**
* Filter assembler for chess events
*/
class ChessFilterAssembler(
client: INostrClient,
) : ComposeSubscriptionManager<ChessQueryState>() {
val group =
listOf(
ChessFeedFilterSubAssembler(client, ::allKeys),
)
override fun invalidateKeys() = invalidateFilters()
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
override fun destroy() = group.forEach { it.destroy() }
}
@@ -18,23 +18,16 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chess
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chess.datasource
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.commons.chess.subscription.ChessFilterBuilder
import com.vitorpamplona.amethyst.commons.chess.subscription.ChessSubscriptionState
import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.KeyDataSourceSubscription
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chess.ChessViewModelNew
/**
* Subscribe to chess events when the Chess screen is active.
@@ -44,8 +37,8 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
*/
@Composable
fun ChessSubscription(
accountViewModel: AccountViewModel,
chessViewModel: ChessViewModelNew,
accountViewModel: AccountViewModel,
) {
// Get active game IDs from the view model for game-specific subscriptions
val activeGames by chessViewModel.activeGames.collectAsStateWithLifecycle()
@@ -85,80 +78,3 @@ fun ChessSubscription(
}
}
}
/**
* Query state for chess subscription
*/
data class ChessQueryState(
val userPubkey: String,
val inboxRelays: Set<NormalizedRelayUrl>,
val globalRelays: Set<NormalizedRelayUrl>,
val isGlobal: Boolean,
val activeGameIds: Set<String> = emptySet(),
val opponentPubkeys: Set<String> = emptySet(),
)
/**
* Filter assembler for chess events
*/
class ChessFilterAssembler(
client: INostrClient,
) : ComposeSubscriptionManager<ChessQueryState>() {
val group =
listOf(
ChessFeedFilterSubAssembler(client, ::allKeys),
)
override fun invalidateKeys() = invalidateFilters()
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
override fun destroy() = group.forEach { it.destroy() }
}
/**
* Sub-assembler that creates the actual relay filters
*/
class ChessFeedFilterSubAssembler(
client: INostrClient,
allKeys: () -> Set<ChessQueryState>,
) : PerUniqueIdEoseManager<ChessQueryState, String>(client, allKeys) {
override fun updateFilter(
key: ChessQueryState,
since: SincePerRelayMap?,
): List<RelayBasedFilter> = filterChessEvents(key, since)
override fun id(key: ChessQueryState): String = "chess-${key.userPubkey}-${key.isGlobal}-${key.activeGameIds.hashCode()}"
}
/**
* Create relay filters for chess events using the shared [ChessFilterBuilder].
*
* Following jesterui's approach:
* 1. Fetch ALL challenges from ALL connected relays (no author/tag restriction)
* 2. Filter client-side based on user's preferences (Global/Follows)
* 3. Game-specific subscriptions for active games
*
* This ensures we see:
* - Open challenges from anyone
* - Challenges directed at us
* - Public games to spectate
*/
fun filterChessEvents(
key: ChessQueryState,
since: SincePerRelayMap?,
): List<RelayBasedFilter> {
// Convert Android ChessQueryState to shared ChessSubscriptionState
val state =
ChessSubscriptionState(
userPubkey = key.userPubkey,
relays = key.inboxRelays + key.globalRelays,
activeGameIds = key.activeGameIds,
opponentPubkeys = key.opponentPubkeys,
)
// Use shared filter builder for consistent behavior with Desktop
return ChessFilterBuilder.buildAllFilters(state) { relay ->
since?.get(relay)?.time
}
}
@@ -0,0 +1,58 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chess.datasource
import com.vitorpamplona.amethyst.commons.chess.subscription.ChessFilterBuilder
import com.vitorpamplona.amethyst.commons.chess.subscription.ChessSubscriptionState
import com.vitorpamplona.amethyst.commons.relays.SincePerRelayMap
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
/**
* Create relay filters for chess events using the shared [ChessFilterBuilder].
*
* Following jesterui's approach:
* 1. Fetch ALL challenges from ALL connected relays (no author/tag restriction)
* 2. Filter client-side based on user's preferences (Global/Follows)
* 3. Game-specific subscriptions for active games
*
* This ensures we see:
* - Open challenges from anyone
* - Challenges directed at us
* - Public games to spectate
*/
fun filterChessEvents(
key: ChessQueryState,
since: SincePerRelayMap?,
): List<RelayBasedFilter> {
// Convert Android ChessQueryState to shared ChessSubscriptionState
val state =
ChessSubscriptionState(
userPubkey = key.userPubkey,
relays = key.inboxRelays + key.globalRelays,
activeGameIds = key.activeGameIds,
opponentPubkeys = key.opponentPubkeys,
)
// Use shared filter builder for consistent behavior with Desktop
return ChessFilterBuilder.buildAllFilters(state) { relay ->
since?.get(relay)?.time
}
}
@@ -20,6 +20,8 @@
*/
package com.vitorpamplona.amethyst.commons.chess
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.normalizeRelayUrl
/**
* Global chess configuration shared across Android and Desktop.
*
@@ -43,9 +45,9 @@ object ChessConfig {
*/
val CHESS_RELAY_NAMES =
listOf(
"relay.damus.io",
"nos.lol",
"relay.primal.net",
"relay.damus.io".normalizeRelayUrl(),
"nos.lol".normalizeRelayUrl(),
"relay.primal.net".normalizeRelayUrl(),
)
/**