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
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- House wallet screen (cu-04b): the operator-only wallet management view
reached from Profile. Generate / Import create the wallet, the balance,
public address and Android-Keystore-backed private key card render with
a biometric prompt overlay, revealing the key starts an auto-hide
countdown, and Backup / Revoke / Recent Activity round out the screen.
The public address is middle-truncated to one line, and the refresh,
copy and Polygonscan actions give feedback via the in-app toast. The
no-biometric error is exercised through @Preview. Replaces the house
wallet placeholder in the nav graph.
- In-app toast host (`ui/components/StackToast.kt`): a top-anchored,
slide-in toast with success / error / info / warn styling mirroring the
mockup, hoisted once in `StackApp` and reachable from any screen via
`LocalToastController`.
- Shared `truncateMiddle` string helper moved to `util/` so the
provably-fair seeds and the house wallet address share one
implementation.
- KYC screen (cu-16): a static, full-screen identity-verification flow
reached from Profile and the wallet withdraw tab. The front and back ID
slots simulate an upload (Empty -> Uploading -> Uploaded) with a mock
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.plainstudio.stackcasino.feature.housewallet

import androidx.compose.ui.test.assertCountEquals
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollTo
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.plainstudio.stackcasino.ui.theme.StackcasinoTheme
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class HouseWalletScreenTest {
@get:Rule
val composeRule = createComposeRule()

private fun setState(
state: HouseWalletUiState,
onBack: () -> Unit = {},
) {
composeRule.setContent {
StackcasinoTheme {
HouseWalletScreenContent(
state = state,
secondsRemaining = 7,
onBack = onBack,
onCreateWallet = {},
onRevealRequest = {},
onHideKey = {},
)
}
}
}

@Test
fun unset_offers_generate_and_import() {
setState(HouseWalletUiState.Unset)

composeRule.onNodeWithText("Generate New Wallet").assertIsDisplayed()
composeRule.onNodeWithText("Import Private Key").performScrollTo().assertIsDisplayed()
}

@Test
fun active_masks_the_key_until_reveal() {
setState(HouseWalletUiState.Wallet(keyRevealed = false))

composeRule.onNodeWithText("Reveal with Biometric").performScrollTo().assertIsDisplayed()
composeRule.onAllNodesWithText("Hide Now").assertCountEquals(0)
}

@Test
fun revealed_shows_the_key_and_the_hide_action() {
setState(HouseWalletUiState.Wallet(keyRevealed = true))

composeRule.onNodeWithText("Hide Now").performScrollTo().assertIsDisplayed()
composeRule.onNodeWithText("0:07").performScrollTo().assertIsDisplayed()
}

@Test
fun back_button_invokes_the_callback() {
var backPressed = false
setState(HouseWalletUiState.Unset, onBack = { backPressed = true })

composeRule.onNodeWithContentDescription("Back").performClick()

assertTrue(backPressed)
}
}
62 changes: 39 additions & 23 deletions app/src/main/java/com/plainstudio/stackcasino/StackApp.kt
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
package com.plainstudio.stackcasino

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.plainstudio.stackcasino.navigation.PrimaryTab
import com.plainstudio.stackcasino.navigation.Route
import com.plainstudio.stackcasino.navigation.StackNavHost
import com.plainstudio.stackcasino.navigation.StartDestination
import com.plainstudio.stackcasino.ui.components.LocalToastController
import com.plainstudio.stackcasino.ui.components.StackBottomBar
import com.plainstudio.stackcasino.ui.components.StackToastHost
import com.plainstudio.stackcasino.ui.components.ToastHostState
import com.plainstudio.stackcasino.ui.theme.StackcasinoTheme

/**
Expand All @@ -33,32 +40,41 @@ fun StackApp(startDestination: StartDestination) {
val navController = rememberNavController()
val backStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = backStackEntry?.destination?.route
val toastState = remember { ToastHostState() }

Scaffold(
modifier = Modifier.fillMaxSize(),
bottomBar = {
if (currentRoute in PrimaryTab.routePaths) {
StackBottomBar(
currentRoute = currentRoute,
onTabSelected = { tab ->
// defaultPath instead of path so parametric tabs
// (Wallet) navigate to the bare destination
// without dragging the {tab} placeholder along.
navController.navigate(tab.route.defaultPath) {
popUpTo(Route.Lobby.path) { saveState = true }
launchSingleTop = true
restoreState = true
}
},
CompositionLocalProvider(LocalToastController provides toastState) {
Box(modifier = Modifier.fillMaxSize()) {
Scaffold(
modifier = Modifier.fillMaxSize(),
bottomBar = {
if (currentRoute in PrimaryTab.routePaths) {
StackBottomBar(
currentRoute = currentRoute,
onTabSelected = { tab ->
// defaultPath instead of path so parametric tabs
// (Wallet) navigate to the bare destination
// without dragging the {tab} placeholder along.
navController.navigate(tab.route.defaultPath) {
popUpTo(Route.Lobby.path) { saveState = true }
launchSingleTop = true
restoreState = true
}
},
)
}
},
) { padding ->
StackNavHost(
navController = navController,
startDestination = startDestination.route,
modifier = Modifier.padding(padding),
)
}
},
) { padding ->
StackNavHost(
navController = navController,
startDestination = startDestination.route,
modifier = Modifier.padding(padding),
)
StackToastHost(
state = toastState,
modifier = Modifier.align(Alignment.TopCenter),
)
}
}
}
}
Loading
Loading