feat(chess): add dismiss/clear-all for finished games in lobby

Persist dismissed game IDs locally (SharedPreferences on Android,
java.util.prefs on Desktop) so completed games can be permanently
hidden from the chess lobby. Adds per-game dismiss button and
"Clear all" action when 2+ finished games exist.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
davotoula
2026-03-24 21:26:50 +01:00
parent 1f34a79676
commit 86fd92e505
14 changed files with 251 additions and 16 deletions
@@ -20,6 +20,7 @@
*/ */
package com.vitorpamplona.amethyst.ui.note.types package com.vitorpamplona.amethyst.ui.note.types
import androidx.activity.compose.LocalActivity
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -97,11 +98,12 @@ fun RenderLiveChessChallenge(
) { ) {
val event = (note.event as? LiveChessGameChallengeEvent) ?: return val event = (note.event as? LiveChessGameChallengeEvent) ?: return
val gameId = event.gameId() val gameId = event.gameId()
val activity = LocalActivity.current as androidx.fragment.app.FragmentActivity
val chessViewModel: ChessViewModelNew = val chessViewModel: ChessViewModelNew =
viewModel( viewModel(
key = "ChessViewModelNew-${accountViewModel.account.userProfile().pubkeyHex}", key = "ChessViewModelNew-${accountViewModel.account.userProfile().pubkeyHex}",
factory = ChessViewModelFactory(accountViewModel.account), factory = ChessViewModelFactory(accountViewModel.account, activity.application),
) )
val isOpenChallenge = event.opponentPubkey() == null val isOpenChallenge = event.opponentPubkey() == null
@@ -102,6 +102,7 @@ fun ChessGameScreen(
factory = factory =
ChessViewModelFactory( ChessViewModelFactory(
accountViewModel.account, accountViewModel.account,
activity.application,
), ),
) )
@@ -45,6 +45,7 @@ import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -96,7 +97,7 @@ fun ChessLobbyScreen(
viewModel( viewModel(
viewModelStoreOwner = activity, viewModelStoreOwner = activity,
key = "ChessViewModelNew-${accountViewModel.account.userProfile().pubkeyHex}", key = "ChessViewModelNew-${accountViewModel.account.userProfile().pubkeyHex}",
factory = ChessViewModelFactory(accountViewModel.account), factory = ChessViewModelFactory(accountViewModel.account, activity.application),
) )
// Subscribe to chess events when screen is visible // Subscribe to chess events when screen is visible
@@ -555,12 +556,22 @@ fun ChessLobbyContent(
if (completedGames.isNotEmpty()) { if (completedGames.isNotEmpty()) {
item { item {
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
Text( Row(
"Finished Games", modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp),
style = MaterialTheme.typography.titleMedium, horizontalArrangement = Arrangement.SpaceBetween,
fontWeight = FontWeight.Bold, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = 8.dp), ) {
) Text(
"Finished Games",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold,
)
if (completedGames.size >= 2) {
TextButton(onClick = { chessViewModel.dismissAllCompletedGames() }) {
Text("Clear all")
}
}
}
} }
items( items(
@@ -585,6 +596,7 @@ fun ChessLobbyContent(
isDraw = game.isDraw, isDraw = game.isDraw,
moveCount = game.moveCount, moveCount = game.moveCount,
onClick = { onSelectGame(game.gameId) }, onClick = { onSelectGame(game.gameId) },
onDismiss = { chessViewModel.dismissCompletedGame(game.gameId) },
avatar = { avatar = {
OverlappingAvatars( OverlappingAvatars(
avatar1Hex = userPubkey, avatar1Hex = userPubkey,
@@ -30,11 +30,12 @@ import com.vitorpamplona.amethyst.model.Account
*/ */
class ChessViewModelFactory( class ChessViewModelFactory(
private val account: Account, private val account: Account,
private val application: android.app.Application,
) : ViewModelProvider.Factory { ) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T { override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(ChessViewModelNew::class.java)) { if (modelClass.isAssignableFrom(ChessViewModelNew::class.java)) {
return ChessViewModelNew(account) as T return ChessViewModelNew(account, application) as T
} }
throw IllegalArgumentException("Unknown ViewModel class") throw IllegalArgumentException("Unknown ViewModel class")
} }
@@ -25,6 +25,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.vitorpamplona.amethyst.commons.chess.ChessBroadcastStatus import com.vitorpamplona.amethyst.commons.chess.ChessBroadcastStatus
import com.vitorpamplona.amethyst.commons.chess.ChessChallenge import com.vitorpamplona.amethyst.commons.chess.ChessChallenge
import com.vitorpamplona.amethyst.commons.chess.ChessDismissedGamesStorage
import com.vitorpamplona.amethyst.commons.chess.ChessLobbyLogic import com.vitorpamplona.amethyst.commons.chess.ChessLobbyLogic
import com.vitorpamplona.amethyst.commons.chess.ChessPollingDefaults import com.vitorpamplona.amethyst.commons.chess.ChessPollingDefaults
import com.vitorpamplona.amethyst.commons.chess.ChessSyncStatus import com.vitorpamplona.amethyst.commons.chess.ChessSyncStatus
@@ -51,6 +52,7 @@ import kotlinx.coroutines.flow.StateFlow
@Stable @Stable
class ChessViewModelNew( class ChessViewModelNew(
private val account: Account, private val account: Account,
application: android.app.Application,
) : ViewModel() { ) : ViewModel() {
// Instance ID for debugging ViewModel sharing // Instance ID for debugging ViewModel sharing
val instanceId = System.identityHashCode(this) val instanceId = System.identityHashCode(this)
@@ -59,6 +61,7 @@ class ChessViewModelNew(
private val publisher = AndroidChessPublisher(account) private val publisher = AndroidChessPublisher(account)
private val fetcher = AndroidRelayFetcher(account) private val fetcher = AndroidRelayFetcher(account)
private val metadataProvider = AndroidMetadataProvider() private val metadataProvider = AndroidMetadataProvider()
private val dismissedStorage = ChessDismissedGamesStorage.create(application)
// Shared business logic (creates its own ChessLobbyState internally) // Shared business logic (creates its own ChessLobbyState internally)
private val logic = private val logic =
@@ -69,6 +72,7 @@ class ChessViewModelNew(
metadataProvider = metadataProvider, metadataProvider = metadataProvider,
scope = viewModelScope, scope = viewModelScope,
pollingConfig = ChessPollingDefaults.android, pollingConfig = ChessPollingDefaults.android,
dismissedStorage = dismissedStorage,
) )
// ============================================ // ============================================
@@ -105,6 +109,10 @@ class ChessViewModelNew(
fun forceRefresh() = logic.forceRefresh() fun forceRefresh() = logic.forceRefresh()
fun dismissCompletedGame(gameId: String) = logic.dismissCompletedGame(gameId)
fun dismissAllCompletedGames() = logic.dismissAllCompletedGames()
/** /**
* Ensure a game ID is being polled for updates. * Ensure a game ID is being polled for updates.
* Call this when entering a game screen. * Call this when entering a game screen.
@@ -20,6 +20,7 @@
*/ */
package com.vitorpamplona.amethyst.ui.screen.loggedIn.home package com.vitorpamplona.amethyst.ui.screen.loggedIn.home
import androidx.activity.compose.LocalActivity
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.FloatingActionButton
@@ -47,11 +48,12 @@ fun NewChessGameButton(
nav: INav, nav: INav,
) { ) {
var showDialog by remember { mutableStateOf(false) } var showDialog by remember { mutableStateOf(false) }
val activity = LocalActivity.current as androidx.fragment.app.FragmentActivity
val chessViewModel: ChessViewModelNew = val chessViewModel: ChessViewModelNew =
viewModel( viewModel(
key = "ChessViewModelNew-${accountViewModel.account.userProfile().pubkeyHex}", key = "ChessViewModelNew-${accountViewModel.account.userProfile().pubkeyHex}",
factory = ChessViewModelFactory(accountViewModel.account), factory = ChessViewModelFactory(accountViewModel.account, activity.application),
) )
FloatingActionButton( FloatingActionButton(
@@ -0,0 +1,53 @@
/*
* 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.commons.chess
import android.content.Context
import android.content.SharedPreferences
actual class ChessDismissedGamesStorage private actual constructor() {
private var prefs: SharedPreferences? = null
actual companion object {
private const val PREFS_NAME = "chess_dismissed_games"
private fun prefsKey(userPubkey: String) = "dismissed_$userPubkey"
actual fun create(context: Any?): ChessDismissedGamesStorage {
val storage = ChessDismissedGamesStorage()
val ctx =
context as? Context
?: throw IllegalArgumentException("Android context required")
storage.prefs = ctx.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
return storage
}
}
// getStringSet returns a live reference to the internal set — must copy defensively
actual fun load(userPubkey: String): Set<String> = prefs?.getStringSet(prefsKey(userPubkey), null)?.toHashSet() ?: emptySet()
actual fun save(
userPubkey: String,
ids: Set<String>,
) {
prefs?.edit()?.putStringSet(prefsKey(userPubkey), ids)?.apply()
}
}
@@ -0,0 +1,38 @@
/*
* 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.commons.chess
/**
* Persists dismissed chess game IDs locally per user.
* Uses expect/actual for platform-specific storage.
*/
expect class ChessDismissedGamesStorage private constructor() {
companion object {
fun create(context: Any? = null): ChessDismissedGamesStorage
}
fun load(userPubkey: String): Set<String>
fun save(
userPubkey: String,
ids: Set<String>,
)
}
@@ -27,8 +27,12 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text import androidx.compose.material3.Text
@@ -289,6 +293,7 @@ fun CompletedGameCard(
isDraw: Boolean, isDraw: Boolean,
moveCount: Int, moveCount: Int,
onClick: (() -> Unit)? = null, onClick: (() -> Unit)? = null,
onDismiss: (() -> Unit)? = null,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
avatar: @Composable (() -> Unit)? = null, avatar: @Composable (() -> Unit)? = null,
) { ) {
@@ -340,6 +345,15 @@ fun CompletedGameCard(
color = resultColor, color = resultColor,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
) )
if (onDismiss != null) {
IconButton(onClick = onDismiss) {
Icon(
Icons.Default.Close,
contentDescription = "Dismiss",
tint = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}
} }
} }
} }
@@ -125,9 +125,15 @@ class ChessLobbyLogic(
private val metadataProvider: IUserMetadataProvider, private val metadataProvider: IUserMetadataProvider,
private val scope: CoroutineScope, private val scope: CoroutineScope,
pollingConfig: ChessPollingConfig = ChessPollingDefaults.android, pollingConfig: ChessPollingConfig = ChessPollingDefaults.android,
private val dismissedStorage: ChessDismissedGamesStorage? = null,
) { ) {
val state = ChessLobbyState(userPubkey, scope) val state = ChessLobbyState(userPubkey, scope)
private val dismissedGameIds: MutableSet<String> =
java.util.Collections.synchronizedSet(
dismissedStorage?.load(userPubkey)?.toMutableSet() ?: mutableSetOf(),
)
// Track when games were last loaded to prevent duplicate fetches // Track when games were last loaded to prevent duplicate fetches
// (e.g., discoverUserGames loads a game, then polling immediately re-fetches it) // (e.g., discoverUserGames loads a game, then polling immediately re-fetches it)
private val recentlyLoadedGames = java.util.concurrent.ConcurrentHashMap<String, Long>() private val recentlyLoadedGames = java.util.concurrent.ConcurrentHashMap<String, Long>()
@@ -869,6 +875,7 @@ class ChessLobbyLogic(
for (startEventId in newGameIds) { for (startEventId in newGameIds) {
if (startEventId in completedGameIds) continue if (startEventId in completedGameIds) continue
if (startEventId in dismissedGameIds) continue
val events = fetcher.fetchGameEvents(startEventId) val events = fetcher.fetchGameEvents(startEventId)
val result = ChessGameLoader.loadGame(events, userPubkey) val result = ChessGameLoader.loadGame(events, userPubkey)
@@ -942,6 +949,19 @@ class ChessLobbyLogic(
return null return null
} }
fun dismissCompletedGame(gameId: String) {
state.removeCompletedGame(gameId)
dismissedGameIds.add(gameId)
dismissedStorage?.save(userPubkey, HashSet(dismissedGameIds))
}
fun dismissAllCompletedGames() {
val allIds = state.completedGames.value.map { it.gameId }
state.clearCompletedGames()
dismissedGameIds.addAll(allIds)
dismissedStorage?.save(userPubkey, HashSet(dismissedGameIds))
}
/** /**
* Dismiss a finished game from the active/spectating list and move it to completed. * Dismiss a finished game from the active/spectating list and move it to completed.
* Called when the user clicks "Continue" on the game end overlay, or automatically * Called when the user clicks "Continue" on the game end overlay, or automatically
@@ -442,6 +442,16 @@ class ChessLobbyState(
} }
} }
fun removeCompletedGame(gameId: String) {
_completedGames.update { current ->
current.filter { it.gameId != gameId }
}
}
fun clearCompletedGames() {
_completedGames.value = emptyList()
}
fun addSpectatingGame( fun addSpectatingGame(
gameId: String, gameId: String,
state: LiveChessGameState, state: LiveChessGameState,
@@ -0,0 +1,51 @@
/*
* 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.commons.chess
import java.util.prefs.Preferences
actual class ChessDismissedGamesStorage private actual constructor() {
private val prefs: Preferences = Preferences.userNodeForPackage(ChessDismissedGamesStorage::class.java)
actual companion object {
private const val NODE_PREFIX = "chess_dismissed_"
private const val DELIMITER = ","
actual fun create(context: Any?): ChessDismissedGamesStorage = ChessDismissedGamesStorage()
}
actual fun load(userPubkey: String): Set<String> {
val raw = prefs.get("$NODE_PREFIX$userPubkey", "")
if (raw.isEmpty()) return emptySet()
return raw.split(DELIMITER).toSet()
}
actual fun save(
userPubkey: String,
ids: Set<String>,
) {
if (ids.isEmpty()) {
prefs.remove("$NODE_PREFIX$userPubkey")
} else {
prefs.put("$NODE_PREFIX$userPubkey", ids.joinToString(DELIMITER))
}
}
}
@@ -53,6 +53,7 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
@@ -339,6 +340,8 @@ fun ChessScreen(
onOpenOwnChallenge = { viewModel.openOwnChallenge(it) }, onOpenOwnChallenge = { viewModel.openOwnChallenge(it) },
onWatchGame = { viewModel.loadGameAsSpectator(it) }, onWatchGame = { viewModel.loadGameAsSpectator(it) },
onSelectGame = { viewModel.selectGame(it) }, onSelectGame = { viewModel.selectGame(it) },
onDismissGame = { viewModel.dismissCompletedGame(it) },
onDismissAllGames = { viewModel.dismissAllCompletedGames() },
listState = listState, listState = listState,
) )
} }
@@ -372,6 +375,8 @@ private fun ChessLobby(
onOpenOwnChallenge: (ChessChallenge) -> Unit, onOpenOwnChallenge: (ChessChallenge) -> Unit,
onWatchGame: (String) -> Unit, onWatchGame: (String) -> Unit,
onSelectGame: (String) -> Unit, onSelectGame: (String) -> Unit,
onDismissGame: (String) -> Unit,
onDismissAllGames: () -> Unit,
listState: LazyListState = rememberLazyListState(), listState: LazyListState = rememberLazyListState(),
) { ) {
val hasContent = val hasContent =
@@ -581,12 +586,22 @@ private fun ChessLobby(
if (completedGames.isNotEmpty()) { if (completedGames.isNotEmpty()) {
item { item {
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
Text( Row(
"Recent Games", modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp),
style = MaterialTheme.typography.titleMedium, horizontalArrangement = Arrangement.SpaceBetween,
fontWeight = FontWeight.Bold, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = 8.dp), ) {
) Text(
"Recent Games",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold,
)
if (completedGames.size >= 2) {
TextButton(onClick = onDismissAllGames) {
Text("Clear all")
}
}
}
} }
items( items(
@@ -603,6 +618,7 @@ private fun ChessLobby(
isDraw = game.isDraw, isDraw = game.isDraw,
moveCount = game.moveCount, moveCount = game.moveCount,
onClick = { onSelectGame(game.gameId) }, onClick = { onSelectGame(game.gameId) },
onDismiss = { onDismissGame(game.gameId) },
avatar = { avatar = {
UserAvatar( UserAvatar(
userHex = opponentPubkey, userHex = opponentPubkey,
@@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.desktop.chess
import com.vitorpamplona.amethyst.commons.chess.ChessBroadcastStatus import com.vitorpamplona.amethyst.commons.chess.ChessBroadcastStatus
import com.vitorpamplona.amethyst.commons.chess.ChessChallenge import com.vitorpamplona.amethyst.commons.chess.ChessChallenge
import com.vitorpamplona.amethyst.commons.chess.ChessDismissedGamesStorage
import com.vitorpamplona.amethyst.commons.chess.ChessLobbyLogic import com.vitorpamplona.amethyst.commons.chess.ChessLobbyLogic
import com.vitorpamplona.amethyst.commons.chess.ChessPollingDefaults import com.vitorpamplona.amethyst.commons.chess.ChessPollingDefaults
import com.vitorpamplona.amethyst.commons.chess.ChessSyncStatus import com.vitorpamplona.amethyst.commons.chess.ChessSyncStatus
@@ -59,6 +60,7 @@ class DesktopChessViewModelNew(
private val publisher = DesktopChessPublisher(account, relayManager) private val publisher = DesktopChessPublisher(account, relayManager)
private val fetcher = DesktopRelayFetcher(relayManager, account.pubKeyHex) private val fetcher = DesktopRelayFetcher(relayManager, account.pubKeyHex)
private val metadataProvider = DesktopMetadataProvider(userMetadataCache) private val metadataProvider = DesktopMetadataProvider(userMetadataCache)
private val dismissedStorage = ChessDismissedGamesStorage.create()
// Shared business logic (creates its own ChessLobbyState internally) // Shared business logic (creates its own ChessLobbyState internally)
private val logic = private val logic =
@@ -69,6 +71,7 @@ class DesktopChessViewModelNew(
metadataProvider = metadataProvider, metadataProvider = metadataProvider,
scope = scope, scope = scope,
pollingConfig = ChessPollingDefaults.desktop, pollingConfig = ChessPollingDefaults.desktop,
dismissedStorage = dismissedStorage,
) )
// ============================================ // ============================================
@@ -164,6 +167,10 @@ class DesktopChessViewModelNew(
fun dismissGame(gameId: String) = logic.dismissGame(gameId) fun dismissGame(gameId: String) = logic.dismissGame(gameId)
fun dismissCompletedGame(gameId: String) = logic.dismissCompletedGame(gameId)
fun dismissAllCompletedGames() = logic.dismissAllCompletedGames()
// ============================================ // ============================================
// Spectator operations // Spectator operations
// ============================================ // ============================================