Preflop App Docs
Table of Contents
Navigation
Components
Constants
Utils
Navigation
Router
The Router.kt file is responsible for setting up and managing the navigation in the app using Jetpack Compose's navigation components and the Accompanist library for animated transitions. This setup allows for smooth navigation between different composable screens in the app.
Route Enum
Defines the various routes (screens) in the app. Each enum value represents a screen.
enum class Route {
Home,
AppInfo,
OpenPosition,
FacingRaise,
Facing3Bet,
Facing4Bet,
SqueezeBlind,
Grid
}
Router Composable Function
The Router function sets up the navigation controller and defines the navigation routes along with their respective animations and themes.
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun Router(viewModelStoreOwner: ViewModelStoreOwner) {
val navController = rememberAnimatedNavController()
val navigateTo = { routeDestination: Route ->
navController.navigate(
routeDestination.name,
navOptions {
anim {
enter = android.R.animator.fade_in
exit = android.R.animator.fade_out
}
}
)
}
Navigation Controller
navController: Manages app navigation and back stack.
Navigation Function
navigateTo: Navigates to a given route with specified animations.
Transition Functions
Define the common transitions for entering and exiting screens.
fun commonEnterTransition(): AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition? = {
slideInHorizontally(initialOffsetX = { it })
}
fun commonExitTransition(): AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition? = {
slideOutHorizontally()
}
fun commonPopEnterTransition(): AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition? = {
slideInHorizontally()
}
fun commonPopExitTransition(): AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition? = {
slideOutHorizontally(targetOffsetX = { it })
}
Animated Navigation Host
Defines the navigation routes and their respective composable screens with animations.
Navigation Routes
Home:
Uses
LightTheme.Wraps content in
TemplateWrapper.Displays the
Homecomposable with navigation and ViewModel.
AppInfo:
Uses
LightTheme.Wraps content in
TemplateWrapper.Displays the
AppInfocomposable with navigation and ViewModel.
Facing3Bet, Facing4Bet, FacingRaise, OpenPosition, SqueezeBlind, Grid:
Use
DarkTheme.Wrap content in
TemplateWrapper.Display the corresponding composable with navigation and ViewModel.
Common Transitions
Each route has common enter and exit transitions defined by commonEnterTransition, commonExitTransition, commonPopEnterTransition, and commonPopExitTransition. These transitions are applied to all routes to ensure consistent navigation animations.
Key Concepts and Logic
Animated Navigation:
Uses the Accompanist library for animated transitions between routes.
Provides a smooth and visually appealing navigation experience.
Composable Navigation:
Defines composable functions for each route.
Uses
AnimatedNavHostto manage the navigation host.
Theming:
Applies different themes (
LightThemeandDarkTheme) based on the route.Ensures a consistent look and feel across different screens.
Navigation Options:
Uses
navOptionsto specify custom animations for navigation actions.Defines enter and exit animations for transitioning between screens.
Components
NavBarHome
Purpose
To provide a navigation bar at the top of the Home screen, allowing users to navigate to different sections of the app or open external links.
Key Functionalities
Navigation Buttons: Includes buttons for navigating to the app info page and an external help URL.
Detailed Explanation
Icon Buttons: Two
IconButtoncomposables are used for navigation. The first navigates to theAppInforoute, and the second opens a help URL in a browser.
@Composable
fun NavBarHome(navigateTo: (destination: Route) -> Unit, navBarData: Map<String, String>) {
val context = LocalContext.current
Surface(modifier = Modifier.background(MaterialTheme.colors.background)) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.height(44.dp).fillMaxWidth().padding(start = 13.dp, end = 13.dp)
) {
IconButton(onClick = { navigateTo(Route.AppInfo) }) {
Image(
painter = painterResource(id = R.drawable.crown_store),
contentDescription = null,
modifier = Modifier.height(18.dp).width(22.dp)
)
}
IconButton(
onClick = {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(navBarData["navBarUrl"]))
context.startActivity(intent)
}
) {
Image(
painter = painterResource(id = R.drawable.help),
contentDescription = null,
modifier = Modifier.height(18.dp).width(22.dp)
)
}
}
}
}LogoHeader
Purpose
To display the main logo and header text at the top of the Home screen.
Key Functionalities
Logo and Title: Displays the application logo and the header title.
Detailed Explanation
Row Layout: Uses a
Rowcomposable to align the logo and title horizontally.
@Composable
fun LogoHeader(logoHeaderData: Map<String, String>) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxWidth().padding(16.dp)
) {
Icon(
painter = painterResource(id = R.drawable.appicon),
contentDescription = null,
tint = orange,
modifier = Modifier.size(41.dp)
)
Text(
text = logoHeaderData["title"] as String,
style = typography.h1,
textAlign = TextAlign.Center,
modifier = Modifier.padding(start = 8.dp),
color = black
)
}
}
ScrollableMenu
Purpose
To provide a scrollable menu of buttons, each navigating to a different section of the application.
Key Functionalities
Scrollable List: Displays a vertical list of buttons that can be scrolled.
Navigation: Each button navigates to a different route.
Detailed Explanation
LazyColumn: Uses a
LazyColumnto create a scrollable list of buttons.Button Creation: Iterates over
menuButtonDatato create buttons dynamically.
@Composable
fun ScrollableMenu(
navigateTo: (destination: Route) -> Unit,
menuButtonData: Array<out Map<String, Any>>,
scrollHeight: Dp
) {
LazyColumn(
verticalArrangement = Arrangement.spacedBy(10.dp),
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth().padding(horizontal = 13.dp).height(scrollHeight)
) {
items(menuButtonData.size) { index ->
val item = menuButtonData[index]
Button(
onClick = { navigateTo(item["route"] as Route) },
shape = RoundedCornerShape(8.dp),
modifier = Modifier.width(343.dp).height(64.dp),
colors = ButtonDefaults.buttonColors(backgroundColor = darkGrey),
elevation = null
) {
Text(
text = item["title"] as String,
modifier = Modifier.weight(1f),
style = typography.button,
color = Color.White
)
}
}
item { Spacer(Modifier.height(10.dp)) }
}
}
LogoFooter
Purpose
To display a footer with a logo and text at the bottom of the Home screen.
Key Functionalities
Footer Content: Displays additional information and branding.
Detailed Explanation
Column Layout: Uses a
Columncomposable to arrange the footer content vertically.
@Composable
fun LogoFooter(logoFooterData: Map<String, String>) {
Column(modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = logoFooterData["topText"] as String,
style = typography.overline,
textAlign = TextAlign.Center,
modifier = Modifier.padding(bottom = 6.dp),
color = lightGrey
)
Spacer(modifier = Modifier.height(3.dp))
Image(
painter = painterResource(id = R.drawable.pokersnowielogo),
contentDescription = null,
modifier = Modifier.width(157.dp).height(23.dp)
)
Spacer(modifier = Modifier.height(6.dp))
Text(
text = logoFooterData["bottomText"] as String,
style = typography.overline,
textAlign = TextAlign.Center,
color = lightGrey
)
}
}
NavBar
This component creates a navigation bar with a title, optional subtitle, and icons for navigation and help. It dynamically adjusts styles based on the theme and the percentage reduction provided by the sharedViewModel.
Explanation of the Key Logic and Complexities
Context and Theme Initialization
val context = LocalContext.current
val scaleFactor = 1 - (sharedViewModel.percentageReduction / 100)
val isLightTheme = MaterialTheme.colors.onBackground == Color.White
Context and Theme:
context: Obtains the current context using
LocalContext.current.scaleFactor: Calculates the scale factor for resizing elements based on the
percentageReductionfromsharedViewModel.isLightTheme: Determines if the current theme is light by checking the background colour.
Icon Resources Selection
val backIcon = if (isLightTheme) R.drawable.backblack else R.drawable.backwhite
val helpIcon = if (isLightTheme) R.drawable.help else R.drawable.helpwhite
Icon Resources:
backIconandhelpIcon: Selects the appropriate icon resources based on the current theme (light or dark).
Surface and Row Layout
Surface(color = Color.Transparent) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.height(44.dp).fillMaxWidth().padding(start = 13.dp, end = 13.dp)
) {
Surface and Row:
Surface: A transparent surface that acts as the container for the navigation bar.
Row: A horizontal layout that arranges its children vertically centered and with specific padding.
Back Button
IconButton(onClick = { navController.navigateUp() }) {
Image(
painter = painterResource(id = backIcon),
contentDescription = null,
modifier = Modifier.height(18.dp).width(22.dp).scale(scaleFactor)
)
}
Back Button:
IconButton: A button with an icon that navigates up when clicked.
Image: Displays the back icon, scaled according to the
scaleFactor.
Title and Subtitle
Column(
modifier = Modifier.weight(1f),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = navBarData["navBarTitle"] as String,
style = typography.h6,
textAlign = TextAlign.Center,
color = if (isLightTheme) darkGrey else white,
modifier = Modifier.scale(scaleFactor)
)
if (subtitle != null) {
Text(
text = subtitle,
style = typography.overline,
textAlign = TextAlign.Center,
color = white,
modifier = Modifier.alpha(0.75f).scale(scaleFactor)
)
}
}
Title and Subtitle:
Column: A vertical layout for the title and optional subtitle, centered horizontally.
Text (Title): Displays the navigation bar title with dynamic color and scaling.
Text (Subtitle): Conditionally displays the subtitle with reduced opacity and scaling.
Help Button
IconButton(
onClick = {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(navBarData["navBarUrl"]))
context.startActivity(intent)
},
enabled = !disableIcon
) {
Image(
painter = painterResource(id = helpIcon),
contentDescription = null,
modifier =
Modifier.height(18.dp)
.width(22.dp)
.alpha(if (disableIcon) 0f else 1f)
.scale(scaleFactor)
)
}
Help Button:
IconButton: A button with an icon that opens a URL when clicked.
enabled: Disables the button if
disableIconis true.
Image: Displays the help icon, scaled and with conditional opacity based on
disableIcon.
LinksHeader
The LinksHeader composable provides a header with clickable links.
Purpose
To provide clickable links for "Privacy Policy" and "License Agreement".
Detailed Explanation
Text: Displays the text for the links with underline and colour styling.
Click Handlers: Opens the respective URLs in a browser when clicked.
@Composable
fun LinksHeader(linksData: Map<String, String>) {
val context = LocalContext.current
Surface {
Row(
modifier = Modifier.fillMaxWidth().padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = buildAnnotatedString {
append(linksData["leftText"] as String)
addStyle(
style = SpanStyle(
textDecoration = TextDecoration.Underline,
color = lightGrey
),
start = 0,
end = (linksData["leftText"] as String).length
)
},
style = typography.body1,
textAlign = TextAlign.Start,
modifier = Modifier.weight(1f).clickable {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(linksData["leftUrl"]))
context.startActivity(intent)
}
)
Text(
text = buildAnnotatedString {
append(linksData["rightText"] as String)
addStyle(
style = SpanStyle(
textDecoration = TextDecoration.Underline,
color = lightGrey
),
start = 0,
end = (linksData["rightText"] as String).length
)
},
style = typography.body1,
textAlign = TextAlign.End,
modifier = Modifier.weight(1f).clickable {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(linksData["rightUrl"]))
context.startActivity(intent)
}
)
}
}
}
InfoBody
The InfoBody composable provides the main body content of the App Info screen.
Purpose
To display the main informational content about the application.
Detailed Explanation
Text: Displays the title and body text with appropriate styling and padding.
@Composable
fun InfoBody(bodyData: Map<String, String>) {
Surface {
Column {
Text(
text = bodyData["title"] as String,
style = typography.h5,
color = darkGrey,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp)
)
Text(
text = bodyData["body"] as String,
style = typography.body1,
color = darkGrey,
modifier = Modifier.padding(horizontal = 16.dp)
)
}
}
}
InfoFooter
The InfoFooter composable provides a footer with a clickable link.
Purpose
To provide a clickable footer link that leads to more information.
Detailed Explanation
Image and Text: Displays an image and a clickable text link that opens a URL in a browser.
@Composable
fun InfoFooter(footerData: Map<String, String>) {
val context = LocalContext.current
Surface {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 25.dp)
) {
Image(
painter = painterResource(id = R.drawable.pokersnowielogo),
contentDescription = null,
modifier = Modifier.height(23.dp).width(157.dp)
)
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = buildAnnotatedString {
append(footerData["linkText"] as String)
addStyle(
style = SpanStyle(color = orange),
start = 0,
end = (footerData["linkText"] as String).length
)
},
style = MaterialTheme.typography.caption,
textAlign = TextAlign.End,
modifier = Modifier.weight(1f).clickable {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(footerData["linkUrl"]))
context.startActivity(intent)
}
)
Spacer(modifier = Modifier.width(4.dp))
Icon(
painter = painterResource(id = R.drawable.snowiearrow),
contentDescription = null,
tint = orange,
modifier = Modifier.height(10.dp).width(15.dp).clickable {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(footerData["linkUrl"]))
context.startActivity(intent)
}
)
}
}
}
}
OptionsCarousel
The OptionsCarousel component provides a carousel-like interface for selecting different options such as table size, stack size, and stake in a poker game. It uses a horizontal pager to navigate through different options and includes indicators to show the current page.
Explanation of Key Logic and Complexities
ExperimentalPagerApi Annotation
@OptIn(ExperimentalPagerApi::class)This annotation is used to opt into the experimental Pager API provided by the accompanist library, which allows for horizontal paging functionality.
State Initialization
val pagerState = rememberPagerState()
val pageIndicators = listOf(0, 1, 2)
val coroutineScope = rememberCoroutineScope()
pagerState: Holds the state of the pager, such as the current page index.
pageIndicators: A list of indices representing the pages in the pager.
coroutineScope: Provides a coroutine scope for launching coroutines, used for animating page transitions.
Dynamic Spacer Height Calculation
val spacerHeight =
if (sharedViewModel.availableHeight >= 90.dp) {
45.dp
} else {
sharedViewModel.availableHeight / 2
}
This calculation adjusts the height of the spacer based on the available height from the sharedViewModel, ensuring the layout adapts to different screen sizes.
Horizontal Pager
HorizontalPager(state = pagerState, count = 3, modifier = Modifier.fillMaxWidth()) { page ->
when (page) {
0 ->
SelectorRow(
title = "TABLE SIZE",
options = listOf("6", "10"),
unit = "SEATS",
selectedOption = "${sharedViewModel.seats}",
onClick = { option ->
sharedViewModel.seats = option.toInt()
onTableSizeSelected(option)
},
sharedViewModel = sharedViewModel
)
1 ->
SelectorRow(
title = "STACK SIZE",
options = listOf("50", "100", "200"),
unit = "BB",
selectedOption = "${sharedViewModel.stackSize}",
onClick = { option -> sharedViewModel.stackSize = option.toInt() },
sharedViewModel = sharedViewModel
)
2 ->
SelectorRow(
title = "STAKE",
options = listOf("Low", "High"),
selectedOption = sharedViewModel.stake,
onClick = { option -> sharedViewModel.stake = option },
sharedViewModel = sharedViewModel
)
}
}
HorizontalPager: A composable that provides horizontal paging functionality.
The pager contains three pages, each displaying a
SelectorRowfor different options (Table Size, Stack Size, Stake).
SelectorRow
SelectorRow is a composable (not shown in the code) that displays the options for each setting. It includes parameters for the title, options, unit, selected option, click handler, and shared view model.
Page Indicators
Row(
Modifier.fillMaxWidth().padding(vertical = 16.dp),
horizontalArrangement = Arrangement.Center
) {
pageIndicators.forEach { pageIndex ->
Box(Modifier.padding(horizontal = 4.dp)) {
PageIndicator(
selected = pagerState.currentPage == pageIndex,
onClick = { targetPageIndex ->
coroutineScope.launch {
pagerState.animateScrollToPage(targetPageIndex)
}
},
pageIndex = pageIndex,
sharedViewModel = sharedViewModel
)
}
}
}
Row: A horizontal layout that contains the page indicators, centered within the parent.
pageIndicators.forEach: Iterates over the list of page indices to create individual indicators.
PageIndicator: A composable that represents an individual page indicator.
PageIndicator Composable Function
@Composable
fun PageIndicator(
selected: Boolean,
onClick: (Int) -> Unit,
pageIndex: Int,
sharedViewModel: SharedViewModel
) {
val color = if (selected) orange else mediumGrey
val coroutineScope = rememberCoroutineScope()
Box(
modifier =
Modifier.size(
if (sharedViewModel.percentageReduction == 0f) {
10.dp
} else {
10.dp * (1 - (sharedViewModel.percentageReduction) / 100)
}
)
.background(color, CircleShape)
.clickable(onClick = { coroutineScope.launch { onClick(pageIndex) } })
)
}
selected: A boolean indicating if the current indicator is selected.
onClick: A callback function to handle clicks on the indicator.
pageIndex: The index of the current indicator.
color: Sets the color of the indicator based on whether it is selected.
coroutineScope: Provides a scope for launching coroutines.
SeatButton
The SeatButton component provides an interactive button representing a seat at a poker table. This component changes appearance based on its state (e.g., Hero, Folded) and includes animations for a more dynamic user experience.
Explanation of Key Logic and Complexities
Enum Class for Seat Status
enum class SeatStatus {
UnSet,
Hero,
HeroRaisedPot,
HeroRaisedHalfPot,
HeroCalled,
Folded,
FoldedDisabled,
RaisedPot,
RaisedHalfPot,
Disabled,
Raised3Bet,
RaisesAnd4Bet,
Called
}
This enum class defines the various states a seat can be in, such as UnSet, Hero, Folded, etc. Each state affects the appearance and behaviour of the SeatButton.
State Management and Initialization
val isDisabled = status.value == SeatStatus.Disabled || status.value == SeatStatus.FoldedDisabled
var animProgress: Float by remember { mutableStateOf(if (isDisabled) 1f else 0f) }
if (!isDisabled) {
LaunchedEffect(key1 = appearDelay) {
delay(appearDelay.toLong())
animProgress = 1f
}
}
val scaleFactor = 1 - (sharedViewModel.percentageReduction / 100)
val circleSize =
if (sharedViewModel.percentageReduction == 0f) {
50.dp
} else {
50.dp * (1 - (sharedViewModel.percentageReduction) / 100)
}
val backgroundColor =
when (status.value) {
SeatStatus.FoldedDisabled,
SeatStatus.Disabled -> darkGrey
SeatStatus.Hero,
SeatStatus.HeroRaisedPot,
SeatStatus.HeroRaisedHalfPot,
SeatStatus.HeroCalled -> white
else -> mediumGrey
}
isDisabled: Checks if the seat is in a disabled state.
animProgress: A float value used to control the animation progress.
LaunchedEffect: Starts a coroutine to handle the appearance delay for the animation.
scaleFactor: Adjusts the size of the button based on the
percentageReductionfrom thesharedViewModel.circleSize: Sets the size of the button.
backgroundColor: Sets the background colour based on the seat's status.
ScaleFadeAnimation Composable
@Composable
fun ScaleFadeAnimation(
targetScale: Float,
animate: Boolean,
durationMillis: Int = 300,
content: @Composable () -> Unit
) {
val initialScale = if (animate) 0f else targetScale
val animationSpec = tween<Float>(durationMillis = durationMillis, easing = LinearEasing)
val scale =
animateFloatAsState(
targetValue = if (animate) targetScale else initialScale,
animationSpec = if (animate) animationSpec else snap()
)
Box(modifier = Modifier.scale(scale.value).alpha(scale.value)) { content() }
}
ScaleFadeAnimation: A composable that scales and fades its content based on the provided
targetScaleandanimateflag.initialScale: Sets the initial scale for the animation.
animationSpec: Defines the animation specification, including duration and easing.
animateFloatAsState: Animates the scale value.
Button and Text Styling
Button(
onClick = onClick,
shape = CircleShape,
border = BorderStroke(3.dp, borderColor),
colors =
ButtonDefaults.buttonColors(
backgroundColor = backgroundColor,
disabledBackgroundColor = backgroundColor
),
modifier =
modifier.size(circleSize).clip(CircleShape).background(Color.Transparent),
enabled = status.value in listOf(SeatStatus.UnSet, SeatStatus.Folded)
) {
Box(modifier = Modifier.size(circleSize)) {}
}
Text(
text = label,
style = typography.h3,
modifier =
Modifier.scale(scaleFactor).align(Alignment.Center).run {
when (status.value) {
SeatStatus.UnSet,
SeatStatus.Folded -> this.clickable { onClick() }
else -> this.clickable(enabled = false) {}
}
},
maxLines = 1,
softWrap = false,
color =
when (status.value) {
SeatStatus.FoldedDisabled,
SeatStatus.Disabled -> grey
SeatStatus.Hero,
SeatStatus.HeroRaisedPot,
SeatStatus.HeroRaisedHalfPot,
SeatStatus.HeroCalled -> darkGrey
else -> white
}
)
Button: Creates a circular button with a border and background colour based on the seat's status. The button is only enabled for
UnSetandFoldedstatuses.Text: Displays the label inside the button, adjusting its colour and clickability based on the seat's status.
Conditional Move Label Text
if (
status.value != SeatStatus.UnSet &&
status.value != SeatStatus.Hero &&
status.value != SeatStatus.Disabled
) {
val moveLabelText =
when (status.value) {
SeatStatus.HeroRaisedPot -> "RAISE POT"
SeatStatus.HeroRaisedHalfPot -> "RAISE 1/2 POT"
SeatStatus.HeroCalled,
SeatStatus.Called -> "CALL"
SeatStatus.Folded,
SeatStatus.FoldedDisabled -> "FOLD"
SeatStatus.RaisedPot -> "RAISE 1 POT"
SeatStatus.RaisedHalfPot -> "RAISE 1/2 POT"
SeatStatus.Raised3Bet -> "RAISE 3BET POT"
SeatStatus.RaisesAnd4Bet ->
if (singlelineLabel) {
"RAISE 1/2 POT & 4BET POT"
} else {
"RAISE 1/2 POT\n& 4BET POT"
}
else -> ""
}
Box(modifier = Modifier.align(Alignment.Center)) {
Text(
text = moveLabelText,
style = ExtraTypographyStyles["overline3"] ?: TextStyle(),
color = white,
modifier = Modifier.scale(scaleFactor).padding(top = 85.dp),
textAlign = TextAlign.Center
)
}
}
Conditionally displays additional move label text based on the seat's status.
moveLabelText: Determines the text to display based on the seat's current status.
The text is displayed inside a
Boxaligned to the center of the button.
SeatsConfigurator
The SeatsConfigurator component is responsible for configuring the seating arrangement at a poker table. It handles different stages of seat configuration and incorporates animations and interactivity to enhance user experience.
SeatsConfigurator Composable Function
@Composable
fun SeatsConfigurator(
sharedViewModel: SharedViewModel,
appearDelay: Int = 100,
onSeatButtonClick: (Int) -> Unit = {},
onResetActionChange: ((IntArray) -> Unit) -> Unit,
resetAction: MutableState<(IntArray) -> Unit>,
seatButtonClick: MutableState<Boolean>,
showArrow: MutableState<Boolean>,
onReset: (IntArray) -> Unit = {},
onPreflopButtonClick: () -> Unit
) {
var resetCounter by remember { mutableStateOf(0) }
val buttonScaleAnim = remember { Animatable(if (sharedViewModel.animate) 0f else 1f) }
val gap = 40.dp
val tableArrowHeight = remember { mutableStateOf(0.dp) }
val seatSize = 50.dp
val scaleFactor = 1 - (sharedViewModel.percentageReduction / 100)
Explanation of State Variables
resetCounter: A counter used to track the number of times the seat configuration has been reset.
buttonScaleAnim: An animation variable to handle the scaling of the button.
gap: The space between the table header and the table.
tableArrowHeight: A mutable state to store the height of the table arrow.
seatSize: The size of each seat button.
scaleFactor: A scale factor derived from the
percentageReductioninsharedViewModel.
Layout of the SeatsConfigurator
Box(
modifier = Modifier.fillMaxWidth().wrapContentHeight().padding(16.dp),
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(id = R.drawable.table_arrow),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier =
Modifier.scale(scaleFactor).size(180.dp).layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
tableArrowHeight.value = placeable.height.toDp()
layout(placeable.width, placeable.height) { placeable.place(0, 0) }
}
)
Box: A container that centers its content.
Image: Displays the table arrow image, scaling and measuring it appropriately.
Displaying the Preflop Button
if (sharedViewModel.showTableButton) {
Image(
painter = painterResource(id = R.drawable.tablebutton),
contentDescription = null,
modifier =
Modifier.align(Alignment.Center)
.size((seatSize * 2.5f) * scaleFactor)
.clip(CircleShape)
.scale(buttonScaleAnim.value)
.clickable { onPreflopButtonClick() },
contentScale = ContentScale.Crop
)
Column(
modifier =
Modifier.align(Alignment.Center)
.size((seatSize * 2.5f) * scaleFactor)
.scale(buttonScaleAnim.value),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
buildAnnotatedString {
append("VIEW\n")
append("PREFLOP\n")
append("ADVICE")
},
style = MaterialTheme.typography.h4,
textAlign = TextAlign.Center,
color = white,
modifier = Modifier.scale(scaleFactor)
)
}
}
Displays the preflop advice button if
sharedViewModel.showTableButtonis true.The button is centered, scaled, and clickable, triggering the
onPreflopButtonClickcallback when clicked.
Custom Layout for Seats and Table Header
Layout(
content = {
TableHeader(
sharedViewModel.tableHeader,
onArrowClick = {
resetAction.value(sharedViewModel.resetPosition.toIntArray())
},
showArrow = showArrow.value,
sharedViewModel = sharedViewModel
)
key(resetCounter) {
if (sharedViewModel.seatStatuses.isNotEmpty()) {
var labelValue = 1
for (i in 0 until sharedViewModel.seats) {
val label =
when (i) {
0 -> "D"
1 -> "SB"
2 -> "BB"
else -> labelValue++.toString()
}
SeatsButton(
status = sharedViewModel.seatStatuses[i],
label = label,
appearDelay = appearDelay,
onClick = { onSeatButtonClick(i) },
sharedViewModel = sharedViewModel
)
}
}
}
},
measurePolicy = { measurables, constraints ->
val placeables = measurables.map { m -> m.measure(constraints) }
val width = constraints.maxWidth
val height = constraints.maxHeight
val centerX = width / 2
val centerY = height / 2
val radius = tableArrowHeight.value.toPx() * scaleFactor
val angle = 2 * PI / sharedViewModel.seats
layout(width, height) {
placeables.forEachIndexed { index, placeable ->
val x: Double
val y: Double
if (index == 0) { // TableHeader
val angleOffset = -PI / 2
y =
centerY + sin(angleOffset) * (radius - seatSize.toPx() / 2f) -
placeable.height / 2
placeable.place(
x = centerX - placeable.width / 2,
y = (y - placeable.height - gap.toPx()).toInt()
)
} else { // SeatButtons
val angleOffset = -PI / 2 - angle
x =
centerX +
cos(index * angle + angleOffset) *
(radius - seatSize.toPx() / 2f) - placeable.width / 2
y =
centerY +
sin(index * angle + angleOffset) *
(radius - seatSize.toPx() / 2f) - placeable.height / 2
placeable.place(x.toInt(), y.toInt())
}
}
}
}
)
}
Layout: Custom layout for placing the table header and seat buttons.
TableHeader: Displays the header for the table.
SeatsButton: Displays buttons for each seat.
Custom measure policy calculates positions based on the center and radius.
Resetting Seat Configuration
fun resetSeatConfigurator(resetPosition: IntArray) {
sharedViewModel.useDefaultOnReset =
(sharedViewModel.currentStep != ConfigurationStep.ChooseCallPosition)
if (sharedViewModel.useDefaultOnReset) {
sharedViewModel.tableHeader = sharedViewModel.resetTitle
sharedViewModel.currentStep = ConfigurationStep.ChooseMyPosition
sharedViewModel.raisedPosition = -1
sharedViewModel.animate = true
resetCounter++
showArrow.value = false
for (i in 0 until sharedViewModel.seats) {
if (resetPosition.contains(i)) {
sharedViewModel.seatStatuses[i].value = SeatStatus.UnSet
} else {
sharedViewModel.seatStatuses[i].value = SeatStatus.Disabled
}
}
} else {
onReset(resetPosition)
}
sharedViewModel.showTableButton = false
}
Resets the seat configurator based on the current step.
If
useDefaultOnResetis true, resets all seats toUnSetorDisabled.Updates the
resetCounter,showArrow, andshowTableButtonstates.
Animation Effects
LaunchedEffect(seatButtonClick.value) {
buttonScaleAnim.snapTo(0f)
if (sharedViewModel.showTableButton) {
buttonScaleAnim.animateTo(
targetValue = 1f,
animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing)
)
}
if (!sharedViewModel.showTableButton) {
buttonScaleAnim.snapTo(0f)
}
}
LaunchedEffect(Unit) { onResetActionChange(::resetSeatConfigurator) }
}
LaunchedEffect: Handles the button scale animation when
seatButtonClickorshowTableButtonchanges.onResetActionChange: Sets the reset action to
resetSeatConfigurator.
SelectorRow
The SelectorRow component is a UI element that provides a row of selectable options, such as table size, stack size, or stake, for users to choose from. This component is used within a column layout and can be displayed either horizontally or vertically, depending on the given parameters.
SelectorRow Composable Function
@Composable
fun SelectorRow(
title: String,
options: List<String>,
unit: String = "",
selectedOption: String,
onClick: (String) -> Unit,
isHorizontal: Boolean = true,
titleColor: Color = orange,
sharedViewModel: SharedViewModel
) {
val scaleFactor = 1 - (sharedViewModel.percentageReduction / 100)
val spacer =
if (sharedViewModel.percentageReduction == 0f) {
16.dp
} else {
16.dp * (1 - (sharedViewModel.percentageReduction) / 100)
}
Explanation of Parameters
title: The title of the selector row.
options: A list of options that the user can select from.
unit: An optional unit label to be displayed next to the option.
selectedOption: The currently selected option.
onClick: A callback function that gets triggered when an option is selected.
isHorizontal: A boolean indicating if the options should be displayed horizontally.
titleColor: The color of the title text.
sharedViewModel: An instance of the shared ViewModel.
Calculation of scaleFactor and spacer
scaleFactor: Adjusts the size of the UI elements based on the
percentageReductionvalue from thesharedViewModel.spacer: Adjusts the space between the option buttons based on the
percentageReductionvalue.
Column Layout
Column(modifier = Modifier.fillMaxWidth()) {
Text(
text = title,
style = MaterialTheme.typography.subtitle1,
color = titleColor,
modifier =
Modifier.scale(scaleFactor)
.align(Alignment.CenterHorizontally)
.padding(bottom = (if (isHorizontal) 22.dp else 12.dp))
)
Column: A layout container that arranges its children in a vertical sequence.
Text: Displays the title of the selector row. It is scaled and centered horizontally. The bottom padding is adjusted based on the
isHorizontalflag.
Box and Row for Options
Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
Row(horizontalArrangement = Arrangement.Center) {
options.forEachIndexed { index, option ->
StyledButton(
selectedOption = selectedOption,
onClick = { onClick(option) },
buttonText1 = option.uppercase(),
buttonText2 = unit,
sharedViewModel = sharedViewModel
)
if (index < options.size - 1) Spacer(modifier = Modifier.width(spacer))
}
}
}
}
}
Box: A container that centers its content both horizontally and vertically.
Row: A horizontal container that arranges its children in a row.
options.forEachIndexed: Iterates over the options list, creating a
StyledButtonfor each option.StyledButton: A custom button composable that displays the option text.
Spacer: Adds space between the buttons, except for the last one.
Key Concepts and Logic
Dynamic Scaling: The
scaleFactorandspacerdynamically adjust the size of UI elements based on thepercentageReductionvalue from thesharedViewModel. This allows the UI to be responsive to different screen sizes and user settings.Composable Reusability: The
SelectorRowfunction is a composable function, meaning it can be easily reused and composed with other UI elements. This composability is a core feature of Jetpack Compose, allowing for more modular and maintainable code.State Management: The
selectedOptionparameter andonClickcallback manage the state of the selected option. When a user selects an option, theonClickfunction updates the state, ensuring the UI reflects the current selection.Layout Flexibility: The
isHorizontalflag provides flexibility in the layout of the options, allowing them to be displayed either horizontally or vertically based on the use case.
StyledButton
The StyledButton component is a custom composable function designed to create a styled button that displays one or two lines of text. This button changes its appearance based on whether it is selected or not.
StyledButton Composable Function
@Composable
fun StyledButton(
selectedOption: String,
onClick: () -> Unit,
buttonText1: String,
buttonText2: String = "",
sharedViewModel: SharedViewModel
) {
val borderColor: Color
val buttonColors =
if (selectedOption.uppercase() != buttonText1) {
borderColor = mediumGrey
ButtonDefaults.buttonColors(backgroundColor = mediumGrey, contentColor = white)
} else {
borderColor = orange
ButtonDefaults.buttonColors(backgroundColor = mediumGrey, contentColor = white)
}
Explanation of Parameters
selectedOption: The currently selected option. Used to determine the button's appearance.
onClick: A callback function that gets triggered when the button is clicked.
buttonText1: The primary text displayed on the button.
buttonText2: The secondary text displayed on the button (optional).
sharedViewModel: An instance of the shared ViewModel.
Determining Button Colors and Border
borderColor: The border colour of the button. It is set to
mediumGreyif the button is not selected, andorangeif it is selected.buttonColors: A
ButtonDefaultsconfiguration that sets the background and content colours of the button.
val scaleFactor = 1 - (sharedViewModel.percentageReduction / 100)
val buttonSize =
if (sharedViewModel.percentageReduction == 0f) {
80.dp
} else {
80.dp * (1 - (sharedViewModel.percentageReduction) / 100)
}
val borderSize =
if (sharedViewModel.percentageReduction == 0f) {
3.dp
} else {
3.dp * (1 - (sharedViewModel.percentageReduction) / 100)
}
Calculation of scaleFactor, buttonSize, and borderSize
scaleFactor: Adjusts the size of the UI elements based on the
percentageReductionvalue from thesharedViewModel.buttonSize: Adjusts the size of the button based on the
percentageReductionvalue.borderSize: Adjusts the size of the button border based on the
percentageReductionvalue.
Button and Text Layout
Box(contentAlignment = Alignment.Center) {
Button(
onClick = onClick,
border = BorderStroke(borderSize, borderColor),
modifier = Modifier.size(buttonSize),
shape = CircleShape,
colors = buttonColors
) {}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = buttonText1,
style =
if (buttonText1 == "LOW" || buttonText1 == "HIGH") {
typography.h3
} else {
typography.h2
},
maxLines = 1,
softWrap = false,
color = white,
modifier = Modifier.scale(scaleFactor)
)
if (buttonText2 != "") {
Text(
text = buttonText2,
maxLines = 1,
softWrap = false,
color = white,
style = typography.body2,
modifier =
Modifier.scale(scaleFactor)
.offset(y = -(typography.h2.fontSize.toDp() * (1 - scaleFactor)))
)
}
}
}
}
Box: A container that centers its content both horizontally and vertically.
Button: The main button element.
onClick: The callback function triggered when the button is clicked.
border: Sets the border of the button using the
borderSizeandborderColor.modifier: Sets the size of the button using the
buttonSize.shape: Sets the shape of the button to a circle.
colors: Sets the button colors using
buttonColors.
Column: A vertical container that arranges its children in a column.
Text: Displays
buttonText1andbuttonText2(if provided).The style of
buttonText1is determined based on its value (eithertypography.h3ortypography.h2).The
modifierscales the text based on thescaleFactor.
Key Concepts and Logic
Dynamic Scaling: The
scaleFactor,buttonSize, andborderSizedynamically adjust the size of UI elements based on thepercentageReductionvalue from thesharedViewModel. This allows the UI to be responsive to different screen sizes and user settings.State Management: The
selectedOptionparameter manages the state of the selected option. The button appearance changes based on whether it matches theselectedOption.Text Styling: The
Textelements inside theColumnare styled based on their content. The style changes dynamically to accommodate different text lengths and contents.
TableHeader
The TableHeader component is a custom composable function designed to create a header for a table configuration UI in the app. It displays a title and an optional arrow button that can trigger a reset action.
TableHeader Composable Function
@Composable
fun TableHeader(
tableHeaderData: Map<String, String>,
onArrowClick: () -> Unit,
showArrow: Boolean,
sharedViewModel: SharedViewModel
) {
val scaleFactor = 1 - (sharedViewModel.percentageReduction / 100)
Surface(color = Color.Transparent) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.height(19.dp).fillMaxWidth().padding(start = 13.dp, end = 13.dp)
) {
IconButton(
onClick = { onArrowClick() },
enabled = showArrow || !sharedViewModel.animate
) {
Image(
painter = painterResource(id = R.drawable.backsituation),
contentDescription = null,
modifier =
Modifier.height(
if (sharedViewModel.percentageReduction == 0f) {
14.dp
} else {
14.dp * (1 - (sharedViewModel.percentageReduction) / 100)
}
)
.width(
if (sharedViewModel.percentageReduction == 0f) {
16.dp
} else {
16.dp * (1 - (sharedViewModel.percentageReduction) / 100)
}
)
.alpha(if (showArrow || !sharedViewModel.animate) 1f else 0f)
)
}
Text(
text = tableHeaderData["tableHeaderTitle"] as String,
modifier = Modifier.weight(1f).scale(scaleFactor),
style = MaterialTheme.typography.subtitle1,
textAlign = TextAlign.Center,
color = orange
)
IconButton(onClick = {}, enabled = false) {
Image(
painter = painterResource(id = R.drawable.backsituation),
contentDescription = null,
modifier =
Modifier.height(
if (sharedViewModel.percentageReduction == 0f) {
14.dp
} else {
14.dp * (1 - (sharedViewModel.percentageReduction) / 100)
}
)
.width(
if (sharedViewModel.percentageReduction == 0f) {
16.dp
} else {
16.dp * (1 - (sharedViewModel.percentageReduction) / 100)
}
)
.alpha(0f)
)
}
}
}
}
Explanation of Parameters
tableHeaderData: A map containing data related to the table header, specifically the title text.
onArrowClick: A callback function that gets triggered when the arrow button is clicked.
showArrow: A boolean value determining whether the arrow button should be visible and enabled.
sharedViewModel: An instance of the shared ViewModel.
Main Layout and Elements
val scaleFactor = 1 - (sharedViewModel.percentageReduction / 100)scaleFactor: Adjusts the size of UI elements based on the
percentageReductionvalue from thesharedViewModel.
Surface(color = Color.Transparent) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.height(19.dp).fillMaxWidth().padding(start = 13.dp, end = 13.dp)
) {
Surface: A composable that provides a material surface with a specified background colour.
Row: A horizontal layout that arranges its children in a row, vertically centered.
Arrow Button and Image
IconButton(
onClick = { onArrowClick() },
enabled = showArrow || !sharedViewModel.animate
) {
Image(
painter = painterResource(id = R.drawable.backsituation),
contentDescription = null,
modifier =
Modifier.height(
if (sharedViewModel.percentageReduction == 0f) {
14.dp
} else {
14.dp * (1 - (sharedViewModel.percentageReduction) / 100)
}
)
.width(
if (sharedViewModel.percentageReduction == 0f) {
16.dp
} else {
16.dp * (1 - (sharedViewModel.percentageReduction) / 100)
}
)
.alpha(if (showArrow || !sharedViewModel.animate) 1f else 0f)
)
}
IconButton: A composable that displays a button with an icon. It triggers
onArrowClickwhen clicked.Image: Displays the arrow icon. The size and visibility are adjusted based on
percentageReductionandshowArrow.
Header Text
Text(
text = tableHeaderData["tableHeaderTitle"] as String,
modifier = Modifier.weight(1f).scale(scaleFactor),
style = MaterialTheme.typography.subtitle1,
textAlign = TextAlign.Center,
color = orange
)
Text: Displays the header title.
text: The title text from
tableHeaderData.modifier: Scales the text based on
scaleFactorand centers it horizontally usingweight(1f).style: Sets the text style to
subtitle1from the Material theme.textAlign: Centers the text alignment.
color: Sets the text colour to
orange.
Placeholder Icon Button
IconButton(onClick = {}, enabled = false) {
Image(
painter = painterResource(id = R.drawable.backsituation),
contentDescription = null,
modifier =
Modifier.height(
if (sharedViewModel.percentageReduction == 0f) {
14.dp
} else {
14.dp * (1 - (sharedViewModel.percentageReduction) / 100)
}
)
.width(
if (sharedViewModel.percentageReduction == 0f) {
16.dp
} else {
16.dp * (1 - (sharedViewModel.percentageReduction) / 100)
}
)
.alpha(0f)
)
}
This
IconButtonserves as a placeholder to maintain symmetry in the layout. It is disabled and has zero opacity.
Key Concepts and Logic
Dynamic Scaling: The
scaleFactoradjusts the size of UI elements based on thepercentageReductionvalue from thesharedViewModel. This makes the UI responsive to different screen sizes and user settings.Conditional Visibility and Enabling: The arrow button's visibility and enabled state are controlled by the
showArrowandsharedViewModel.animatevalues. This allows the arrow to be shown or hidden and enabled or disabled based on the application's state.Composable Reusability: The
TableHeaderfunction is a composable function, meaning it can be easily reused and composed with other UI elements. This composability is a core feature of Jetpack Compose, allowing for more modular and maintainable code.Material Design: The component uses Material Design elements such as
Surface,Text, andIconButton, ensuring a consistent and modern look and feel.
Pages
Home
Purpose
The Home composable function sets up the main structure and layout of the Home screen. It initializes the SharedViewModel with default values and arranges the UI components using Jetpack Compose.
Key Functionalities
ViewModel Initialization: Sets up default values for the
SharedViewModel.Layout Composition: Arranges the UI components in a vertical layout using
ColumnandBox.
Detailed Explanation
Initialization: Ensures the home screen starts with a consistent state by initializing various properties of the
SharedViewModel.
val sharedViewModel: SharedViewModel = viewModel(viewModelStoreOwner)
sharedViewModel.seats = 6
sharedViewModel.animate = true
sharedViewModel.stackSize = 50
sharedViewModel.stake = "Low"
sharedViewModel.seatStatuses.clear()
sharedViewModel.showTableButton = false
sharedViewModel.resetPosition.clear()
sharedViewModel.currentStep = ConfigurationStep.ChooseMyPosition
sharedViewModel.raisedPosition = -1
Layout: Uses
ColumnandBoxcomposables to structure the home screen. TheColumnarranges the components vertically, and theBoxprovides a background and additional layout control.
Column(modifier = Modifier.fillMaxSize()) {
Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.background)) {
Column {
NavBarHome(navigateTo = navigateTo, HomeData.navBarData)
Spacer(modifier = Modifier.height(18.dp))
LogoHeader(HomeData.logoHeaderData)
Spacer(modifier = Modifier.height(18.dp))
ScrollableMenu(
navigateTo = navigateTo,
menuButtonData = HomeData.menuButtonData,
scrollHeight = scrollHeight
)
}
Row(horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()) {
Icon(
painter = painterResource(id = R.drawable.bglogo),
contentDescription = null,
modifier = Modifier.size(210.dp).alpha(0.04f).zIndex(-1f).padding(end = 8.dp)
)
}
Box(modifier = Modifier.align(Alignment.BottomCenter)) {
LogoFooter(HomeData.logoFooterData)
}
}
}
AppInfo
The AppInfo composable is the main entry point for the App Info screen, tying together the various components and managing their layout.
Purpose
To assemble the App Info screen using the provided components and data, and manage navigation and state.
Detailed Explanation
Box and Column Layout: Organizes the components in a vertical layout with appropriate background and sizing.
Components: Uses
NavBar,LinksHeader,InfoBody, andInfoFootercomponents, passing in the appropriate data and state.
@Composable
fun AppInfo(navController: NavHostController, viewModelStoreOwner: ViewModelStoreOwner) {
val sharedViewModel: SharedViewModel = viewModel(viewModelStoreOwner)
Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.background)) {
Column {
NavBar(AppInfoData.navBarData, navController, true, sharedViewModel = sharedViewModel)
LinksHeader(linksData = AppInfoData.linksHeaderData)
InfoBody(bodyData = AppInfoData.infoBodyData)
InfoFooter(footerData = AppInfoData.infoFooterData)
}
}
}
OpenPosition
This page allows users to configure various poker table settings such as the number of seats, stack size, and positions of players in an Open Position.
Explanation of the Key Logic and Complexities
ViewModel Initialization and State Management
val sharedViewModel: SharedViewModel = viewModel(viewModelStoreOwner)
sharedViewModel.resetPosition.addAll(listOf(0, 1, 3, 4, 5))
for (idx in 0 until sharedViewModel.seats) {
sharedViewModel.seatStatuses.add(
remember { mutableStateOf(if (idx == 2) SeatStatus.Disabled else SeatStatus.UnSet) }
)
}
val resetAction = remember { mutableStateOf<(IntArray) -> Unit>({}) }
val showArrow = remember { mutableStateOf(false) }
val seatButtonClick = remember { mutableStateOf(false) }
ViewModel Initialization:
sharedViewModel: Obtains the shared
ViewModelinstance usingviewModel(viewModelStoreOwner).resetPosition: Adds positions
[0, 1, 3, 4, 5]toresetPosition, indicating the seats to be reset.seatStatuses: Initializes seat statuses. It iterates over the number of seats (
sharedViewModel.seats), setting the Big Blind (idx == 2) toSeatStatus.Disabledand others toSeatStatus.UnSet.
State Variables:
resetAction: Stores a reset action function as a mutable state.
showArrow: Tracks the visibility of an arrow indicator.
seatButtonClick: Tracks the state of seat button clicks.
OptionsCarousel Component
OptionsCarousel(
onTableSizeSelected = { selectedTableSize ->
sharedViewModel.seats = selectedTableSize.toInt()
tableSizeChanged(
sharedViewModel.seats,
sharedViewModel.seatStatuses,
sharedViewModel.resetPosition,
resetAction,
(0 until sharedViewModel.seats).filter { it != 2 }
)
},
sharedViewModel = sharedViewModel
)
OptionsCarousel: Displays a carousel for selecting table size, stack size, and stake.
onTableSizeSelected:
Updates the number of seats in
sharedViewModel.Calls
tableSizeChangedto update seat statuses and reset positions accordingly.Excludes the Big Blind position (
idx == 2) from being reset.
SeatsConfigurator Component
SeatsConfigurator(
sharedViewModel = sharedViewModel,
onSeatButtonClick = { index ->
seatButtonClick.value = !seatButtonClick.value
showArrow.value = true
var positionIndex = 3
var heroSet = false
for (i in 0 until sharedViewModel.seats - 1) {
if (positionIndex == index) {
sharedViewModel.seatStatuses[positionIndex].value = SeatStatus.Hero
heroSet = true
sharedViewModel.heroPosition = index
} else if (!heroSet) {
sharedViewModel.seatStatuses[positionIndex].value = SeatStatus.Folded
} else if (sharedViewModel.resetPosition.contains(positionIndex)) {
sharedViewModel.seatStatuses[positionIndex].value = SeatStatus.UnSet
} else {
sharedViewModel.seatStatuses[positionIndex].value = SeatStatus.Disabled
}
positionIndex = (positionIndex + 1) % sharedViewModel.seats
}
sharedViewModel.showTableButton = true
},
onResetActionChange = { action -> resetAction.value = action },
resetAction = resetAction,
seatButtonClick = seatButtonClick,
showArrow = showArrow
) {
sharedViewModel.jsonFilePrefix =
"OpenRaise${sharedViewModel.seats}_${sharedViewModel.heroPosition}"
navigateTo(Route.Grid)
}
SeatsConfigurator: Manages the configuration of seat statuses.
onSeatButtonClick:
Toggles the seat button click state and shows the arrow indicator.
Iterates through seats to set the hero position and update seat statuses:
If the seat is the selected index, set it as
Hero.If the hero is not set, set preceding seats as
Folded.Reset positions are marked as
UnSet.Remaining seats are marked as
Disabled.
Shows the table button if a hero position is set.
onResetActionChange: Updates the reset action function.
Navigating to Grid:
Sets the JSON file prefix for the selected configuration.
Navigates to the
Gridroute.
Facing3Bet
This page allows users to configure scenarios where they face a 3-bet after raising preflop.
Explanation of the Key Logic and Complexities
ViewModel Initialization and State Management
val sharedViewModel: SharedViewModel = viewModel(viewModelStoreOwner)
sharedViewModel.resetPosition.addAll(listOf(0, 1, 3, 4, 5))
for (idx in 0 until sharedViewModel.seats) {
sharedViewModel.seatStatuses.add(
remember { mutableStateOf(if (idx == 2) SeatStatus.Disabled else SeatStatus.UnSet) }
)
}
val resetAction = remember { mutableStateOf<(IntArray) -> Unit>({}) }
val showArrow = remember { mutableStateOf(false) }
val seatButtonClick = remember { mutableStateOf(false) }
ViewModel Initialization:
sharedViewModel: Obtains the shared
ViewModelinstance usingviewModel(viewModelStoreOwner).resetPosition: Adds positions
[0, 1, 3, 4, 5]toresetPosition, indicating the seats to be reset.seatStatuses: Initializes seat statuses. It iterates over the number of seats (
sharedViewModel.seats), setting the Big Blind (idx == 2) toSeatStatus.Disabledand others toSeatStatus.UnSet.
State Variables:
resetAction: Stores a reset action function as a mutable state.
showArrow: Tracks the visibility of an arrow indicator.
seatButtonClick: Tracks the state of seat button clicks.
UI Composition
Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.background)) {
Column {
NavBar(Facing3BetData.navBarData, navController, sharedViewModel = sharedViewModel)
OptionsCarousel(
onTableSizeSelected = { selectedTableSize ->
sharedViewModel.seats = selectedTableSize.toInt()
tableSizeChanged(
sharedViewModel.seats,
sharedViewModel.seatStatuses,
sharedViewModel.resetPosition,
resetAction,
(0 until sharedViewModel.seats).filter { it != 2 }
)
},
sharedViewModel = sharedViewModel
)
Box and Column Layout:
Sets up the main container with a background and full screen size.
NavBar Component:
Displays the navigation bar using
Facing3BetDatafor data,navControllerfor navigation, andsharedViewModelfor state management.
OptionsCarousel Component:
Allows the user to select table size.
Updates the number of seats in
sharedViewModeland callstableSizeChangedto adjust the seat statuses and reset positions.
SeatsConfigurator
SeatsConfigurator(
sharedViewModel = sharedViewModel,
onSeatButtonClick = { index ->
seatButtonClick.value = !seatButtonClick.value
val availableRaises = mutableListOf<Int>()
if (sharedViewModel.currentStep == ConfigurationStep.ChooseMyPosition) {
var positionIndex = 3
var heroSet = false
for (i in 0 until sharedViewModel.seats) {
when {
positionIndex == index -> {
sharedViewModel.heroPosition = index
sharedViewModel.seatStatuses[positionIndex].value =
if (index in listOf(1, 2)) {
SeatStatus.HeroRaisedPot
} else {
SeatStatus.HeroRaisedHalfPot
}
heroSet = true
}
!heroSet -> {
sharedViewModel.seatStatuses[positionIndex].value =
SeatStatus.FoldedDisabled
}
else -> availableRaises.add(positionIndex)
}
positionIndex = (positionIndex + 1) % sharedViewModel.seats
}
sharedViewModel.currentStep = ConfigurationStep.ChooseRaisePosition
showArrow.value = true
sharedViewModel.tableHeader = Facing3BetData.menuButtonData[1]
updateButtonSeatStates(
availableRaises,
SeatStatus.Raised3Bet,
sharedViewModel
)
if (availableRaises.size == 1) {
sharedViewModel.raisedPosition = availableRaises.firstOrNull() ?: -1
}
} else {
for (idx in 0 until sharedViewModel.seats) {
when {
idx == sharedViewModel.heroPosition -> continue
index == idx -> {
sharedViewModel.raisedPosition = index
sharedViewModel.seatStatuses[idx].value = SeatStatus.Raised3Bet
}
else -> {
if (
sharedViewModel.seatStatuses[idx].value !=
SeatStatus.FoldedDisabled
) {
sharedViewModel.seatStatuses[idx].value = SeatStatus.Folded
}
}
}
}
sharedViewModel.showTableButton = true
}
},
onResetActionChange = { action -> resetAction.value = action },
resetAction = resetAction,
seatButtonClick = seatButtonClick,
showArrow = showArrow
) {
sharedViewModel.jsonFilePrefix =
"Facing3BetAfterMyRaise${sharedViewModel.seats}" +
"_${sharedViewModel.heroPosition}_${sharedViewModel.raisedPosition}"
navigateTo(Route.Grid)
}
}
}
SeatsConfigurator Component:
onSeatButtonClick: Manages seat button click logic.
State Management: Toggles
seatButtonClickto update UI.Available Raises Collection: Collects positions eligible for raising.
Step 1: Choosing Hero Position:
Index Handling:
positionIndexstarts at 3 and iterates through all seats.Hero Position: Sets
heroPositionand updates seat status based on the index.Preceding Positions: Marks positions before the hero as
FoldedDisabled.Collecting Raise Positions: Adds positions after the hero to
availableRaises.State Transition: Updates
currentStep, shows the arrow, updatestableHeader, and callsupdateButtonSeatStatesto mark raise positions.Single Raise Position: Directly sets
raisedPositionif only one raise position is available.
Step 2: Choosing Raise Position:
Skipping Hero Position: Iterates over seats, skipping the hero position.
Setting Raised Position: Sets
raisedPositionand updates seat status toRaised3Bet.Marking Other Positions: Marks non-selected positions as
Foldedunless they areFoldedDisabled.Showing Table Button: Enables the table button by setting
showTableButtonto true.
Reset Action Handling:
Updates the
resetActionmutable state with a new reset function passed through theonResetActionChangecallback.
Facing4Bet
This page allows users to configure various poker table settings when facing a 4-bet scenario. Users can select the number of seats, stack size, and configure player positions.
Explanation of the Key Logic and Complexities
ViewModel Initialization and State Management
val sharedViewModel: SharedViewModel = viewModel(viewModelStoreOwner)
sharedViewModel.resetPosition.addAll(listOf(0, 1, 2, 4, 5))
for (idx in 0 until sharedViewModel.seats) {
sharedViewModel.seatStatuses.add(
remember { mutableStateOf(if (idx == 3) SeatStatus.Disabled else SeatStatus.UnSet) }
)
}
val resetAction = remember { mutableStateOf<(IntArray) -> Unit>({}) }
val showArrow = remember { mutableStateOf(false) }
val seatButtonClick = remember { mutableStateOf(false) }
ViewModel Initialization:
sharedViewModel: Obtains the shared ViewModel instance using
viewModel(viewModelStoreOwner).resetPosition: Adds positions [0, 1, 2, 4, 5] to
resetPosition, indicating the seats to be reset.seatStatuses: Initializes seat statuses. It iterates over the number of seats (
sharedViewModel.seats), setting the seat at index 3 toSeatStatus.Disabledand others toSeatStatus.UnSet.
State Variables:
resetAction: Stores a reset action function as a mutable state.
showArrow: Tracks the visibility of an arrow indicator.
seatButtonClick: Tracks the state of seat button clicks.
UI Composition
Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.background)) {
Column {
NavBar(Facing4BetData.navBarData, navController, sharedViewModel = sharedViewModel)
OptionsCarousel(
onTableSizeSelected = { selectedTableSize ->
sharedViewModel.seats = selectedTableSize.toInt()
tableSizeChanged(
sharedViewModel.seats,
sharedViewModel.seatStatuses,
sharedViewModel.resetPosition,
resetAction,
(0 until sharedViewModel.seats).filter { it != 3 }
)
},
sharedViewModel = sharedViewModel
)
Box and Column Layout:
Sets up the main container with a background and full screen size.
NavBar Component:
Displays the navigation bar using
Facing4BetDatafor data,navControllerfor navigation, andsharedViewModelfor state management.
OptionsCarousel Component:
Allows the user to select table size.
Updates the number of seats in
sharedViewModeland callstableSizeChangedto adjust the seat statuses and reset positions.
SeatsConfigurator
SeatsConfigurator(
sharedViewModel = sharedViewModel,
onSeatButtonClick = { index ->
seatButtonClick.value = !seatButtonClick.value
val availableRaises = mutableListOf<Int>()
if (sharedViewModel.currentStep == ConfigurationStep.ChooseMyPosition) {
var positionIndex = 3
var heroSet = false
for (i in 0 until sharedViewModel.seats) {
when {
positionIndex == index -> {
sharedViewModel.heroPosition = index
sharedViewModel.seatStatuses[positionIndex].value =
SeatStatus.HeroRaisedPot
heroSet = true
}
!heroSet -> {
sharedViewModel.seatStatuses[positionIndex].value =
SeatStatus.UnSet
availableRaises.add(positionIndex)
}
else ->
sharedViewModel.seatStatuses[positionIndex].value =
SeatStatus.FoldedDisabled
}
positionIndex = (positionIndex + 1) % sharedViewModel.seats
}
sharedViewModel.currentStep = ConfigurationStep.ChooseRaisePosition
showArrow.value = true
sharedViewModel.tableHeader = Facing4BetData.menuButtonData[1]
updateButtonSeatStates(
availableRaises,
SeatStatus.RaisesAnd4Bet,
sharedViewModel
)
if (availableRaises.size == 1) {
sharedViewModel.raisedPosition = availableRaises.firstOrNull() ?: -1
}
} else {
for (idx in 0 until sharedViewModel.seats) {
when {
idx == sharedViewModel.heroPosition -> continue
index == idx -> {
sharedViewModel.raisedPosition = index
sharedViewModel.seatStatuses[idx].value =
SeatStatus.RaisesAnd4Bet
}
else -> {
if (
sharedViewModel.seatStatuses[idx].value !=
SeatStatus.Disabled &&
sharedViewModel.seatStatuses[idx].value !=
SeatStatus.FoldedDisabled
) {
sharedViewModel.seatStatuses[idx].value = SeatStatus.Folded
}
}
}
}
sharedViewModel.showTableButton = true
}
},
onResetActionChange = { action -> resetAction.value = action },
resetAction = resetAction,
seatButtonClick = seatButtonClick,
showArrow = showArrow
) {
sharedViewModel.jsonFilePrefix =
"Facing4BetAfterMy3Bet${sharedViewModel.seats}" +
"_${sharedViewModel.heroPosition}_${sharedViewModel.raisedPosition}"
navigateTo(Route.Grid)
}
}
}
SeatsConfigurator Component:
onSeatButtonClick: Manages seat button click logic.
State Management: Toggles
seatButtonClickto update UI.Available Raises Collection: Collects positions eligible for raising.
Step 1: Choosing Hero Position:
Index Handling:
positionIndexstarts at 3 and iterates through all seats.Hero Position: Sets
heroPositionand updates seat status based on the index.Preceding Positions: Marks positions before the hero as
UnSetand collects available raises.State Transition: Updates
currentStep, shows the arrow, updatestableHeader, and callsupdateButtonSeatStatesto mark raise positions.Single Raise Position: Directly sets
raisedPositionif only one raise position is available.
Step 2: Choosing Raise Position:
Skipping Hero Position: Iterates over seats, skipping the hero position.
Setting Raised Position: Sets
raisedPositionand updates seat status toRaisesAnd4Bet.Marking Other Positions: Marks non-selected positions as
Foldedunless they areDisabledorFoldedDisabled.Showing Table Button: Enables the table button by setting
showTableButtonto true.
Reset Action Handling:
Updates the
resetActionmutable state with a new reset function passed through theonResetActionChangecallback.
FacingRise
This page allows users to configure various poker table settings when facing a raise scenario. Users can select the number of seats, stack size, and configure player positions.
Explanation of the Key Logic and Complexities
ViewModel Initialization and State Management
val sharedViewModel: SharedViewModel = viewModel(viewModelStoreOwner)
sharedViewModel.resetPosition.addAll(listOf(0, 1, 2, 4, 5))
for (idx in 0 until sharedViewModel.seats) {
sharedViewModel.seatStatuses.add(
remember { mutableStateOf(if (idx == 3) SeatStatus.Disabled else SeatStatus.UnSet) }
)
}
val resetAction = remember { mutableStateOf<(IntArray) -> Unit>({}) }
val showArrow = remember { mutableStateOf(false) }
val seatButtonClick = remember { mutableStateOf(false) }
ViewModel Initialization:
sharedViewModel: Obtains the shared ViewModel instance using
viewModel(viewModelStoreOwner).resetPosition: Adds positions [0, 1, 2, 4, 5] to
resetPosition, indicating the seats to be reset.seatStatuses: Initializes seat statuses. It iterates over the number of seats (
sharedViewModel.seats), setting the seat at index 3 toSeatStatus.Disabledand others toSeatStatus.UnSet.
State Variables:
resetAction: Stores a reset action function as a mutable state.
showArrow: Tracks the visibility of an arrow indicator.
seatButtonClick: Tracks the state of seat button clicks.
UI Composition
Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.background)) {
Column {
NavBar(FacingRaiseData.navBarData, navController, sharedViewModel = sharedViewModel)
OptionsCarousel(
onTableSizeSelected = { selectedTableSize ->
sharedViewModel.seats = selectedTableSize.toInt()
tableSizeChanged(
sharedViewModel.seats,
sharedViewModel.seatStatuses,
sharedViewModel.resetPosition,
resetAction,
(0 until sharedViewModel.seats).filter { it != 3 }
)
},
sharedViewModel = sharedViewModel
)
Box and Column Layout:
Sets up the main container with a background and full screen size.
NavBar Component:
Displays the navigation bar using
FacingRaiseDatafor data,navControllerfor navigation, andsharedViewModelfor state management.
OptionsCarousel Component:
Allows the user to select table size.
Updates the number of seats in
sharedViewModeland callstableSizeChangedto adjust the seat statuses and reset positions.
SeatsConfigurator
SeatsConfigurator(
sharedViewModel = sharedViewModel,
onSeatButtonClick = { index ->
seatButtonClick.value = !seatButtonClick.value
val availableRaises = mutableListOf<Int>()
if (sharedViewModel.currentStep == ConfigurationStep.ChooseMyPosition) {
var positionIndex = 3
var heroSet = false
for (i in 0 until sharedViewModel.seats) {
when {
positionIndex == index -> {
sharedViewModel.heroPosition = index
sharedViewModel.seatStatuses[positionIndex].value =
if (index == 1) SeatStatus.HeroCalled else SeatStatus.Hero
heroSet = true
}
!heroSet -> {
sharedViewModel.seatStatuses[positionIndex].value =
SeatStatus.UnSet
availableRaises.add(positionIndex)
}
else ->
sharedViewModel.seatStatuses[positionIndex].value =
SeatStatus.Disabled
}
positionIndex = (positionIndex + 1) % sharedViewModel.seats
}
sharedViewModel.currentStep = ConfigurationStep.ChooseRaisePosition
showArrow.value = true
sharedViewModel.tableHeader = FacingRaiseData.menuButtonData[1]
updateButtonSeatStates(
availableRaises,
if (listOf(1, 2).contains(index)) {
SeatStatus.RaisedPot
} else {
SeatStatus.RaisedHalfPot
},
sharedViewModel
)
if (availableRaises.size == 1) {
sharedViewModel.raisedPosition = availableRaises.firstOrNull() ?: -1
}
} else {
for (idx in 0 until sharedViewModel.seats) {
when {
idx == sharedViewModel.heroPosition -> continue
index == idx -> {
sharedViewModel.raisedPosition = index
sharedViewModel.seatStatuses[idx].value =
if (listOf(1, 2).contains(index)) {
SeatStatus.RaisedPot
} else {
SeatStatus.RaisedHalfPot
}
}
else -> {
if (
sharedViewModel.seatStatuses[idx].value !=
SeatStatus.Disabled
) {
sharedViewModel.seatStatuses[idx].value = SeatStatus.Folded
}
}
}
}
sharedViewModel.showTableButton = true
}
},
onResetActionChange = { action -> resetAction.value = action },
resetAction = resetAction,
seatButtonClick = seatButtonClick,
showArrow = showArrow
) {
sharedViewModel.jsonFilePrefix =
"FacingRaise${sharedViewModel.seats}_${sharedViewModel.heroPosition}" +
"_${sharedViewModel.raisedPosition}"
navigateTo(Route.Grid)
}
SeatsConfigurator Component:
onSeatButtonClick: Manages seat button click logic.
State Management: Toggles
seatButtonClickto update UI.Available Raises Collection: Collects positions eligible for raising.
Step 1: Choosing Hero Position:
Index Handling:
positionIndexstarts at 3 and iterates through all seats.Hero Position: Sets
heroPositionand updates seat status based on the index.Preceding Positions: Marks positions before the hero as
UnSetand collects available raises.State Transition: Updates
currentStep, shows the arrow, updatestableHeader, and callsupdateButtonSeatStatesto mark raise positions.Single Raise Position: Directly sets
raisedPositionif only one raise position is available.
Step 2: Choosing Raise Position:
Skipping Hero Position: Iterates over seats, skipping the hero position.
Setting Raised Position: Sets
raisedPositionand updates seat status toRaisedPotorRaisedHalfPotbased on the index.Marking Other Positions: Marks non-selected positions as
Foldedunless they areDisabled.Showing Table Button: Enables the table button by setting
showTableButtonto true.
Reset Action Handling:
Updates the
resetActionmutable state with a new reset function passed through theonResetActionChangecallback.
SqueezeBlind
This page allows users to configure various poker table settings when squeezing from the blinds. Users can select the number of seats, stack size, and configure player positions.
Explanation of the Key Logic and Complexities
ViewModel Initialization and State Management
val sharedViewModel: SharedViewModel = viewModel(viewModelStoreOwner)
sharedViewModel.resetPosition.addAll(listOf(1, 2))
for (idx in 0 until sharedViewModel.seats) {
sharedViewModel.seatStatuses.add(
remember {
mutableStateOf(
if (sharedViewModel.resetPosition.contains(idx)) {
SeatStatus.UnSet
} else {
SeatStatus.Disabled
}
)
}
)
}
val resetAction = remember { mutableStateOf<(IntArray) -> Unit>({}) }
val showArrow = remember { mutableStateOf(false) }
val seatButtonClick = remember { mutableStateOf(false) }
ViewModel Initialization:
sharedViewModel: Obtains the shared ViewModel instance using
viewModel(viewModelStoreOwner).resetPosition: Adds positions [1, 2] to
resetPosition, indicating the seats to be reset.seatStatuses: Initializes seat statuses. It iterates over the number of seats (
sharedViewModel.seats), setting the specified reset positions toSeatStatus.UnSetand others toSeatStatus.Disabled.
State Variables:
resetAction: Stores a reset action function as a mutable state.
showArrow: Tracks the visibility of an arrow indicator.
seatButtonClick: Tracks the state of seat button clicks.
OptionsCarousel Component
OptionsCarousel(
onTableSizeSelected = { selectedTableSize ->
sharedViewModel.seats = selectedTableSize.toInt()
sharedViewModel.currentStep = ConfigurationStep.ChooseMyPosition
tableSizeChanged(
sharedViewModel.seats,
sharedViewModel.seatStatuses,
sharedViewModel.resetPosition,
resetAction,
listOf(1, 2)
)
},
sharedViewModel = sharedViewModel
)
OptionsCarousel:
Displays a carousel for selecting table size, stack size, and stake.
onTableSizeSelected:
Updates the number of seats in
sharedViewModel.Sets the current step to
ChooseMyPosition.Calls
tableSizeChangedto update seat statuses and reset positions accordingly, including positions 1 and 2 for reset.
SeatsConfigurator Component
SeatsConfigurator(
sharedViewModel = sharedViewModel,
onSeatButtonClick = { index ->
seatButtonClick.value = !seatButtonClick.value
val availableRaises = mutableListOf<Int>()
if (sharedViewModel.currentStep == ConfigurationStep.ChooseMyPosition) {
var positionIndex = 3
var heroSet = false
for (i in 0 until sharedViewModel.seats) {
when {
positionIndex == index -> {
sharedViewModel.heroPosition = index
sharedViewModel.seatStatuses[positionIndex].value = SeatStatus.Hero
heroSet = true
}
!heroSet -> {
availableRaises.add(positionIndex)
}
else -> sharedViewModel.seatStatuses[positionIndex].value = SeatStatus.Disabled
}
positionIndex = (positionIndex + 1) % sharedViewModel.seats
}
sharedViewModel.seatStatuses[availableRaises.last()].value = SeatStatus.Disabled
availableRaises.removeLast()
sharedViewModel.currentStep = ConfigurationStep.ChooseRaisePosition
showArrow.value = true
sharedViewModel.tableHeader = SqueezeBlindData.menuButtonData[1]
updateButtonSeatStates(
availableRaises,
if (sharedViewModel.resetPosition.contains(index)) {
SeatStatus.RaisedPot
} else {
SeatStatus.RaisedHalfPot
},
sharedViewModel
)
} else if (sharedViewModel.currentStep == ConfigurationStep.ChooseRaisePosition) {
for (idx in 0 until sharedViewModel.seats) {
if (idx == sharedViewModel.heroPosition) {
continue
} else if (index == idx) {
sharedViewModel.raisedPosition = index
sharedViewModel.seatStatuses[idx].value =
if (sharedViewModel.resetPosition.contains(index)) {
SeatStatus.RaisedPot
} else {
SeatStatus.RaisedHalfPot
}
break
}
}
// compute possible calls
val possibleCalls = mutableListOf<Int>()
var positionIndex: Int
for (idx in 0 until sharedViewModel.seats) {
positionIndex = (sharedViewModel.raisedPosition + idx) % sharedViewModel.seats
if (positionIndex == sharedViewModel.raisedPosition) {
continue
} else if (positionIndex == sharedViewModel.heroPosition) {
break
} else {
possibleCalls.add(positionIndex)
}
}
// set the folded
for (idx in 0 until sharedViewModel.seats) {
positionIndex = (sharedViewModel.raisedPosition + idx) % sharedViewModel.seats
if (
positionIndex == sharedViewModel.raisedPosition ||
positionIndex == sharedViewModel.heroPosition ||
possibleCalls.contains(positionIndex) ||
(sharedViewModel.resetPosition.contains(positionIndex))
) {
continue
}
sharedViewModel.seatStatuses[positionIndex].value = SeatStatus.FoldedDisabled
}
sharedViewModel.currentStep = ConfigurationStep.ChooseCallPosition
showArrow.value = true
sharedViewModel.tableHeader = SqueezeBlindData.menuButtonData[2]
updateButtonSeatStates(possibleCalls, SeatStatus.Called, sharedViewModel)
if (possibleCalls.size == 1) {
sharedViewModel.callPosition = possibleCalls.firstOrNull() ?: -1
sharedViewModel.showTableButton = true
}
} else {
var positionIndex: Int
for (idx in 0 until sharedViewModel.seats) {
positionIndex = (sharedViewModel.raisedPosition + idx) % sharedViewModel.seats
if (
positionIndex == sharedViewModel.raisedPosition ||
positionIndex == sharedViewModel.heroPosition
) {
continue
} else if (positionIndex == index) {
sharedViewModel.callPosition = positionIndex
sharedViewModel.seatStatuses[positionIndex].value = SeatStatus.Called
continue
}
if (
!listOf(SeatStatus.Disabled, SeatStatus.FoldedDisabled)
.contains(sharedViewModel.seatStatuses[positionIndex].value)
) {
sharedViewModel.seatStatuses[positionIndex].value = SeatStatus.Folded
}
}
sharedViewModel.showTableButton = true
}
},
onResetActionChange = { action -> resetAction.value = action },
resetAction = resetAction,
seatButtonClick = seatButtonClick,
showArrow = showArrow,
onReset = {
val availableRaises = mutableListOf<Int>()
var positionIndex = 3
var heroSet = false
for (i in 0 until sharedViewModel.seats) {
if (positionIndex == sharedViewModel.heroPosition) {
heroSet = true
} else if (!heroSet) {
availableRaises.add(positionIndex)
} else {
sharedViewModel.seatStatuses[positionIndex].value = SeatStatus.Disabled
}
positionIndex = (positionIndex + 1) % sharedViewModel.seats
}
sharedViewModel.seatStatuses[availableRaises.last()].value = SeatStatus.Disabled
availableRaises.removeLast()
sharedViewModel.currentStep = ConfigurationStep.ChooseRaisePosition
showArrow.value = true
sharedViewModel.tableHeader = SqueezeBlindData.menuButtonData[1]
updateButtonSeatStates(
availableRaises,
SeatStatus.RaisedHalfPot,
sharedViewModel
)
}
) {
sharedViewModel.jsonFilePrefix =
"SqueezeFromBlind${sharedViewModel.seats}_${sharedViewModel.heroPosition}" +
"_${sharedViewModel.raisedPosition}_${sharedViewModel.callPosition}"
navigateTo(Route.Grid)
}
SeatsConfigurator Component:
onSeatButtonClick: Manages seat button click logic.
State Management: Toggles
seatButtonClickto update UI.Available Raises Collection: Collects positions eligible for raising.
Step 1: Choosing Hero Position:
Index Handling:
positionIndexstarts at 3 and iterates through all seats.Hero Position: Sets
heroPositionand updates seat status based on the index.Preceding Positions: Marks positions before the hero as
UnSetand collects available raises.State Transition: Updates
currentStep, shows the arrow, updatestableHeader, and callsupdateButtonSeatStatesto mark raise positions.Single Raise Position: Directly sets
raisedPositionif only one raise position is available.
Step 2: Choosing Raise Position:
Skipping Hero Position: Iterates over seats, skipping the hero position.
Setting Raised Position: Sets
raisedPositionand updates seat status toRaisedPotorRaisedHalfPotbased on the index.Computing Possible Calls: Collects positions eligible for calling after a raise.
Setting Folded Positions: Marks non-selected positions as
FoldedDisabledunless they are in reset positions.
Step 3: Choosing Call Position:
Skipping Hero and Raise Positions: Iterates over seats, skipping the hero and raise positions.
Setting Call Position: Sets
callPositionand updates seat status toCalled.Marking Other Positions: Marks non-selected positions as
Foldedunless they areDisabled.
Reset Action Handling:
Updates the
resetActionmutable state with a new reset function passed through theonResetActionChangecallback.
Constants
HomeData
The HomeData object contains static data used in the Home screen components. This includes data for the navigation bar, header, menu buttons, and footer.
Purpose
To centralize and manage all the static textual content and configuration used across the Home page components, ensuring consistency and ease of maintenance.
Structure
object HomeData {
val navBarData = mapOf("navBarUrl" to "https://www.pokersnowie.com/preflop-advisor.html#help")
val logoHeaderData = mapOf("title" to "Preflop")
val menuButtonData = arrayOf(
mapOf("route" to Route.OpenPosition, "title" to "Open Position"),
mapOf("route" to Route.FacingRaise, "title" to "Facing a Raise"),
mapOf("route" to Route.Facing3Bet, "title" to "Facing a 3Bet after my Raise"),
mapOf("route" to Route.Facing4Bet, "title" to "Facing a 4Bet after my 3Bet"),
mapOf("route" to Route.SqueezeBlind, "title" to "Squeeze from the Blinds")
)
val logoFooterData = mapOf("topText" to "POWERED BY", "bottomText" to "version 2.1 (27)")
}
Detailed Explanation
navBarData: Contains the URL for the help section in the navigation bar.logoHeaderData: Holds the title for the logo header section.menuButtonData: An array of maps where each map contains a route and title for the menu buttons.logoFooterData: Contains the top and bottom text for the footer.
AppInfoData
The AppInfoData object contains static data used in the App Info screen components. This includes data for the navigation bar, links header, info body, and footer.
Purpose
To centralize and manage all the static textual content and configuration used across the App Info page components, ensuring consistency and ease of maintenance.
Structure
package com.snowiegroup.preflopadvisor.constants
object AppInfoData {
val navBarData = mapOf("navBarTitle" to "Preflop")
val linksHeaderData = mapOf(
"leftText" to "Privacy Policy",
"leftUrl" to "https://pokersnowie.com/privacy-policy.html",
"rightText" to "License Agreement",
"rightUrl" to "https://play.google.com/about/developer-distribution-agreement.html"
)
val infoBodyData = mapOf(
"title" to "Looking for a poker training tool?",
"body" to "PokerSnowie is a leading-edge Artificial Intelligence based poker software, " +
"used since years by thousands of players and coaches around the world. " +
"Advanced training and real time analysis of any possible " +
"Texas Hold'em No Limit situation."
)
val infoFooterData = mapOf("linkText" to "LEARN MORE", "linkUrl" to "https://pokersnowie.com/")
}
Detailed Explanation
navBarData: Contains the title for the navigation bar.
linksHeaderData: Contains text and URLs for the links header section.
infoBodyData: Contains the title and body text for the info body section.
infoFooterData: Contains the text and URL for the footer link.
OpenPositionData
The OpenPositionData object contains static data used in the Open Position screen components. This data is used to configure the navigation bar and menu button, providing a consistent user experience and centralizing static content for easier maintenance and updates.
Purpose
To centralize and manage all the static textual content and configuration used across the Open Position page components, ensuring consistency and ease of maintenance.
Structure
package com.snowiegroup.preflopadvisor.constants
object OpenPositionData {
val navBarData = mapOf(
"navBarTitle" to "Open Position",
"navBarUrl" to "https://www.pokersnowie.com/preflop-advisor.html#help"
)
val menuButtonData = arrayOf(mapOf("tableHeaderTitle" to "WHAT’S MY POSITION?"))
}
Detailed Explanation
navBarData
val navBarData = mapOf(
"navBarTitle" to "Open Position",
"navBarUrl" to "https://www.pokersnowie.com/preflop-advisor.html#help"
)
navBarTitle: Sets the title for the navigation bar to "Open Position".
navBarUrl: Provides a URL for the help documentation related to the Open Position.
menuButtonData
val menuButtonData = arrayOf(mapOf("tableHeaderTitle" to "WHAT’S MY POSITION?"))
Utils
ConfiguratorUtils
The ConfiguratorUtils.kt file contains utility functions that assist in managing the state and behaviour of seat buttons in a poker table configuration UI. These utilities are used to update seat statuses and handle changes in table size.
updateButtonSeatStates Function
fun updateButtonSeatStates(
buttonsList: List<Int>,
singleStatus: SeatStatus,
sharedViewModel: SharedViewModel
) {
if (buttonsList.size == 1) {
sharedViewModel.seatStatuses[buttonsList.first()].value = singleStatus
sharedViewModel.showTableButton = true
} else {
for (arIdx in buttonsList) {
sharedViewModel.seatStatuses[arIdx].value = SeatStatus.UnSet
}
}
}Parameters
buttonsList: A list of integers representing the indices of the buttons to be updated.
singleStatus: The
SeatStatusto be assigned if there is only one button in the list.sharedViewModel: An instance of the shared
SharedViewModel.
Function Logic
Single Button Update:
If
buttonsListcontains only one element, the corresponding seat status is set tosingleStatus.sharedViewModel.showTableButtonis set totrue, indicating that the table button should be displayed.
Multiple Buttons Update:
If
buttonsListcontains multiple elements, the corresponding seat statuses are set toSeatStatus.UnSet.
Usage
This function is typically used to update the status of seat buttons when the user makes a selection. For example, if the user selects a seat to act from, this function updates the status of that seat and potentially enables the table button.
tableSizeChanged Function
fun tableSizeChanged(
tableSize: Int,
seatStatuses: MutableList<MutableState<SeatStatus>>,
resetPosition: MutableList<Int>,
resetAction: MutableState<(IntArray) -> Unit>,
enabledIndices: List<Int>
) {
resetPosition.clear()
seatStatuses.clear()
for (idx in 0 until tableSize) {
if (enabledIndices.contains(idx)) {
seatStatuses.add(mutableStateOf(SeatStatus.UnSet))
resetPosition.add(idx)
} else {
seatStatuses.add(mutableStateOf(SeatStatus.Disabled))
}
}
resetAction.value(resetPosition.toIntArray())
}
Parameters
tableSize: The new size of the table.
seatStatuses: A mutable list of mutable states representing the statuses of the seats.
resetPosition: A mutable list of integers representing the positions to be reset.
resetAction: A mutable state holding a function that resets the positions.
enabledIndices: A list of integers representing the indices of the seats that should be enabled.
Function Logic
Clear Existing States:
Clears the
resetPositionandseatStatuseslists to remove any previous states.
Initialize New States:
Iterates over the range from
0totableSize.For each index:
If the index is in
enabledIndices, it addsSeatStatus.UnSettoseatStatusesand the index toresetPosition.Otherwise, it adds
SeatStatus.DisabledtoseatStatuses.
Set Reset Action:
Updates the
resetActionwith the newresetPositionconverted to an array.
Usage
This function is used to handle changes in the table size. When the table size changes (e.g., from 6 seats to 10 seats), this function updates the seat statuses and the reset positions accordingly.
Key Concepts and Logic
State Management:
Both functions heavily rely on managing mutable states to update the UI reactively.
By using
MutableState, any changes to seat statuses automatically trigger recompositions of the affected UI elements.
Dynamic UI Updates:
The functions enable dynamic updates to the UI based on user interactions, such as selecting a seat or changing the table size.
This dynamic behavior is essential for creating interactive and responsive UIs in Jetpack Compose.
ViewModel Integration:
The functions interact with the
SharedViewModelto maintain a consistent state across different components.This integration ensures that the UI reflects the latest state managed by the ViewModel.
GridUtils
The GridUtils.kt file contains utility functions that assist in handling scrollbar rendering and reading JSON data from assets. These utilities are designed to enhance the user interface by providing a custom scrollbar and to facilitate data access by reading JSON files from the assets directory.
scrollbar Modifier
The scrollbar function creates a custom scrollbar for a LazyListState. It supports both horizontal and vertical scrollbars, with customizable appearance and behaviour.
fun Modifier.scrollbar(
state: LazyListState,
horizontal: Boolean,
percentageReduction: Float,
alignEnd: Boolean = true,
thickness: Dp = 4.dp,
fixedKnobRatio: Float? = null,
knobCornerRadius: Dp = 4.dp,
trackCornerRadius: Dp = 2.dp,
knobColor: Color = white,
trackColor: Color = mediumGrey,
padding: Dp = 0.dp
): Modifier = composed {
drawWithContent {
drawContent()
if (percentageReduction != 0f) {
state.layoutInfo.visibleItemsInfo.firstOrNull()?.let { firstVisibleItem ->
val viewportSize =
if (horizontal) {
size.width
} else {
size.height
} - padding.toPx() * 2
val firstItemSize = firstVisibleItem.size
val estimatedFullListSize = firstItemSize * state.layoutInfo.totalItemsCount
val viewportOffsetInFullListSpace =
state.firstVisibleItemIndex * firstItemSize + state.firstVisibleItemScrollOffset
val knobPosition =
(viewportSize / estimatedFullListSize) *
viewportOffsetInFullListSpace + padding.toPx()
val knobSize =
fixedKnobRatio?.let { it * viewportSize }
?: ((viewportSize * viewportSize) / estimatedFullListSize)
drawRoundRect(
color = trackColor,
topLeft =
when {
horizontal && alignEnd -> Offset(
padding.toPx(),
size.height - thickness.toPx()
)
horizontal && !alignEnd -> Offset(padding.toPx(), 0f)
alignEnd -> Offset(size.width - thickness.toPx(), padding.toPx())
else -> Offset(0f, padding.toPx())
},
size =
if (horizontal) {
Size(size.width - padding.toPx() * 2, thickness.toPx())
} else {
Size(thickness.toPx(), size.height - padding.toPx() * 2)
},
cornerRadius = CornerRadius(
x = trackCornerRadius.toPx(),
y = trackCornerRadius.toPx()
)
)
drawRoundRect(
color = knobColor,
topLeft =
when {
horizontal && alignEnd -> Offset(
knobPosition,
size.height - thickness.toPx()
)
horizontal && !alignEnd -> Offset(knobPosition, 0f)
alignEnd -> Offset(size.width - thickness.toPx(), knobPosition)
else -> Offset(0f, knobPosition)
},
size =
if (horizontal) {
Size(knobSize, thickness.toPx())
} else {
Size(thickness.toPx(), knobSize)
},
cornerRadius = CornerRadius(
x = knobCornerRadius.toPx(),
y = knobCornerRadius.toPx()
)
)
}
}
}
}
Parameters
state: The
LazyListStaterepresenting the scroll state of the list.horizontal: A boolean indicating if the scrollbar is horizontal or vertical.
percentageReduction: A float value representing the reduction percentage for scaling purposes.
alignEnd: A boolean indicating if the scrollbar should be aligned to the end of the container.
thickness: The thickness of the scrollbar.
fixedKnobRatio: An optional fixed ratio for the knob size.
knobCornerRadius: The corner radius for the knob.
trackCornerRadius: The corner radius for the track.
knobColor: The colour of the knob.
trackColor: The colour of the track.
padding: The padding around the scrollbar.
Function Logic
Draw Content:
Calls
drawContentto render the underlying content.
Calculate Scrollbar Dimensions:
Calculates the dimensions of the viewport and the knob based on the visible items and their sizes.
Adjusts the position and size of the knob and track based on the scroll state.
Draw Track and Knob:
Draws the track using
drawRoundRect.Draws the knob using
drawRoundRect, adjusting its position and size based on the scroll state.
Usage
This function is used to add a custom scrollbar to a LazyListState in Jetpack Compose. It provides a highly customizable scrollbar that can be used to enhance the user interface.
readJsonFromAsset Function
The readJsonFromAsset function reads a JSON file from the assets directory and returns its content as a string.
fun readJsonFromAsset(context: Context, fileName: String): String? {
return try {
val inputStream = context.assets.open(fileName)
val size = inputStream.available()
val buffer = ByteArray(size)
inputStream.read(buffer)
inputStream.close()
String(buffer, StandardCharsets.UTF_8)
} catch (e: IOException) {
e.printStackTrace()
Log.e("readJsonFromAsset", "Error: ${e.message} Filename: $fileName")
null
}
}
Parameters
context: The application context.
fileName: The name of the JSON file to be read.
Return Value
Returns the content of the JSON file as a string, or
nullif an error occurs.
Function Logic
Open InputStream:
Opens an input stream to the specified JSON file in the assets directory.
Read File Content:
Reads the content of the file into a byte array.
Converts the byte array to a string using UTF-8 encoding.
Handle Errors:
Catches
IOExceptionand logs an error message if an error occurs during file reading.
Usage
This function is used to read JSON files from the assets directory, allowing the application to load configuration or data files at runtime. It simplifies the process of accessing asset files and handling potential errors.
Key Concepts and Logic
Modifier Composability:
The
scrollbarfunction uses thecomposedmodifier to create a composable modifier that can be applied to any layout.This allows for reusable and customizable UI components in Jetpack Compose.
State Management:
The
scrollbarfunction relies on theLazyListStateto determine the scroll position and update the UI accordingly.By using mutable states and recomposition, the UI automatically updates in response to changes in scroll state.
Error Handling:
The
readJsonFromAssetfunction includes error handling to gracefully handle potential issues when reading files from the assets directory.This ensures robustness and reliability in file I/O operations.