Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Real "Last Played" highlight on the lobby (cu-03): the new
`LobbyViewModel` reads the most recent round from the Room `game_round`
ledger and the lobby marks that game's card with the existing LAST
PLAYED badge, instead of the static preview default. No game played yet
means no card is marked. Covered by `LobbyViewModelTest`.

- Playable Blackjack (cu-06): the fifth and final game, completing the
games card. `BlackjackViewModel` -> `GameRepository.startBlackjack`
draws the shuffled 52-card deck on the C++/JNI engine (committed via the
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.plainstudio.stackcasino.feature.lobby

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.plainstudio.stackcasino.domain.game.GameRepository
import com.plainstudio.stackcasino.model.GameKey
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import javax.inject.Inject

/**
* Backs the lobby with the bits of real round data it needs today: the
* last game the user played, so its card can carry the "LAST PLAYED"
* highlight. Reads the Room-backed [GameRepository.rounds] (newest first),
* so the most recent round's game is the last played, or null when no
* round has been played yet.
*/
@HiltViewModel
class LobbyViewModel
@Inject
constructor(
gameRepository: GameRepository,
) : ViewModel() {
val lastPlayedGame: StateFlow<GameKey?> =
gameRepository.rounds
.map { rounds -> rounds.firstOrNull()?.game }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(STOP_TIMEOUT_MILLIS), null)

private companion object {
const val STOP_TIMEOUT_MILLIS = 5_000L
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import com.plainstudio.stackcasino.feature.housewallet.HouseWalletScreen
import com.plainstudio.stackcasino.feature.kyc.KycScreen
import com.plainstudio.stackcasino.feature.lobby.LobbyScreen
import com.plainstudio.stackcasino.feature.lobby.LobbyUiState
import com.plainstudio.stackcasino.feature.lobby.LobbyViewModel
import com.plainstudio.stackcasino.feature.lobby.previewLobbyData
import com.plainstudio.stackcasino.feature.mines.MinesScreen
import com.plainstudio.stackcasino.feature.mines.MinesViewModel
Expand Down Expand Up @@ -262,11 +263,14 @@ private fun NavGraphBuilder.addLobbyRoute(navController: NavHostController) {
val unreadCount by notificationsViewModel.unreadCount.collectAsStateWithLifecycle()
val walletViewModel: WalletViewModel = hiltViewModel()
val wallet by walletViewModel.walletData.collectAsStateWithLifecycle()
val lobbyViewModel: LobbyViewModel = hiltViewModel()
val lastPlayed by lobbyViewModel.lastPlayedGame.collectAsStateWithLifecycle()
var showNotifications by rememberSaveable { mutableStateOf(false) }
val lobbyData =
previewLobbyData().let {
it.copy(
balance = it.balance.copy(amountLabel = wallet.availableLabel),
games = it.games.map { card -> card.copy(isLastPlayed = card.key == lastPlayed) },
)
}
LobbyScreen(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.plainstudio.stackcasino.feature.lobby

import app.cash.turbine.test
import com.plainstudio.stackcasino.domain.game.GameRepository
import com.plainstudio.stackcasino.domain.game.GameRound
import com.plainstudio.stackcasino.model.GameKey
import com.plainstudio.stackcasino.model.RoundOutcome
import com.plainstudio.stackcasino.testing.MainDispatcherRule
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test

class LobbyViewModelTest {
@get:Rule
val mainDispatcherRule = MainDispatcherRule()

private val gameRepository = mockk<GameRepository>(relaxed = true)

@Test
fun `last played is the most recent round's game`() =
runTest {
every { gameRepository.rounds } returns flowOf(listOf(round(GameKey.Mines), round(GameKey.Crash)))

LobbyViewModel(gameRepository).lastPlayedGame.test {
var value = awaitItem()
while (value == null) value = awaitItem()
assertEquals(GameKey.Mines, value)
cancelAndIgnoreRemainingEvents()
}
}

@Test
fun `last played is null with no rounds`() =
runTest {
every { gameRepository.rounds } returns flowOf(emptyList())

LobbyViewModel(gameRepository).lastPlayedGame.test {
assertEquals(null, awaitItem())
cancelAndIgnoreRemainingEvents()
}
}

private fun round(game: GameKey) =
GameRound(
id = "round-id",
game = game,
pick = "",
stake = 10.0,
payout = 0.0,
outcome = RoundOutcome.Loss,
multiplier = 0.0,
currency = "USDC",
serverSeed = "s",
clientSeed = "c",
nonce = 1L,
hash = "h",
resultRaw = "",
timestamp = 0L,
)
}
Loading