Preflop App Docs

Table of Contents

  1. Navigation

  2. Components

  3. Constants

  4. 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 Home composable with navigation and ViewModel.

  • AppInfo:

    • Uses LightTheme.

    • Wraps content in TemplateWrapper.

    • Displays the AppInfo composable 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 AnimatedNavHost to manage the navigation host.

  • Theming:

    • Applies different themes (LightTheme and DarkTheme) based on the route.

    • Ensures a consistent look and feel across different screens.

  • Navigation Options:

    • Uses navOptions to 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 IconButton composables are used for navigation. The first navigates to the AppInfo route, 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 Row composable 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 LazyColumn to create a scrollable list of buttons.

  • Button Creation: Iterates over menuButtonData to 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 Column composable 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 percentageReduction from sharedViewModel.

  • 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:

  • backIcon and helpIcon: 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 disableIcon is 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 SelectorRow for 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 percentageReduction from the sharedViewModel.

  • 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 targetScale and animate flag.

  • 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 UnSet and Folded statuses.

  • 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 Box aligned 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 percentageReduction in sharedViewModel.

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.showTableButton is true.

  • The button is centered, scaled, and clickable, triggering the onPreflopButtonClick callback 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 useDefaultOnReset is true, resets all seats to UnSet or Disabled.

  • Updates the resetCounter, showArrow, and showTableButton states.

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 seatButtonClick or showTableButton changes.

  • 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 percentageReduction value from the sharedViewModel.

  • spacer: Adjusts the space between the option buttons based on the percentageReduction value.

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 isHorizontal flag.

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 StyledButton for 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 scaleFactor and spacer dynamically adjust the size of UI elements based on the percentageReduction value from the sharedViewModel. This allows the UI to be responsive to different screen sizes and user settings.

  • Composable Reusability: The SelectorRow function 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 selectedOption parameter and onClick callback manage the state of the selected option. When a user selects an option, the onClick function updates the state, ensuring the UI reflects the current selection.

  • Layout Flexibility: The isHorizontal flag 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 mediumGrey if the button is not selected, and orange if it is selected.

  • buttonColors: A ButtonDefaults configuration 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 percentageReduction value from the sharedViewModel.

  • buttonSize: Adjusts the size of the button based on the percentageReduction value.

  • borderSize: Adjusts the size of the button border based on the percentageReduction value.

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 borderSize and borderColor.

    • 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 buttonText1 and buttonText2 (if provided).

      • The style of buttonText1 is determined based on its value (either typography.h3 or typography.h2).

      • The modifier scales the text based on the scaleFactor.

Key Concepts and Logic

  • Dynamic Scaling: The scaleFactor, buttonSize, and borderSize dynamically adjust the size of UI elements based on the percentageReduction value from the sharedViewModel. This allows the UI to be responsive to different screen sizes and user settings.

  • State Management: The selectedOption parameter manages the state of the selected option. The button appearance changes based on whether it matches the selectedOption.

  • Text Styling: The Text elements inside the Column are 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 percentageReduction value from the sharedViewModel.

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 onArrowClick when clicked.

  • Image: Displays the arrow icon. The size and visibility are adjusted based on percentageReduction and showArrow.

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 scaleFactor and centers it horizontally using weight(1f).

    • style: Sets the text style to subtitle1 from 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 IconButton serves as a placeholder to maintain symmetry in the layout. It is disabled and has zero opacity.

Key Concepts and Logic

  • Dynamic Scaling: The scaleFactor adjusts the size of UI elements based on the percentageReduction value from the sharedViewModel. 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 showArrow and sharedViewModel.animate values. This allows the arrow to be shown or hidden and enabled or disabled based on the application's state.

  • Composable Reusability: The TableHeader function 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, and IconButton, 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 Column and Box.

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 Column and Box composables to structure the home screen. The Column arranges the components vertically, and the Box provides 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, and InfoFooter components, 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) }
  1. ViewModel Initialization:

    • sharedViewModel: Obtains the shared ViewModel instance using viewModel(viewModelStoreOwner).

    • resetPosition: Adds positions [0, 1, 3, 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 Big Blind (idx == 2) to SeatStatus.Disabled and others to SeatStatus.UnSet.

  2. 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
)
  1. OptionsCarousel: Displays a carousel for selecting table size, stack size, and stake.

  2. onTableSizeSelected:

    • Updates the number of seats in sharedViewModel.

    • Calls tableSizeChanged to 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)
}
  1. SeatsConfigurator: Manages the configuration of seat statuses.

  2. 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.

  3. onResetActionChange: Updates the reset action function.

  4. Navigating to Grid:

    • Sets the JSON file prefix for the selected configuration.

    • Navigates to the Grid route.

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) }
  1. ViewModel Initialization:

    • sharedViewModel: Obtains the shared ViewModel instance using viewModel(viewModelStoreOwner).

    • resetPosition: Adds positions [0, 1, 3, 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 Big Blind (idx == 2) to SeatStatus.Disabled and others to SeatStatus.UnSet.

  2. 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 Facing3BetData for data, navController for navigation, and sharedViewModel for state management.

OptionsCarousel Component:

  • Allows the user to select table size.

  • Updates the number of seats in sharedViewModel and calls tableSizeChanged to 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 seatButtonClick to update UI.

    • Available Raises Collection: Collects positions eligible for raising.

  • Step 1: Choosing Hero Position:

    • Index Handling: positionIndex starts at 3 and iterates through all seats.

    • Hero Position: Sets heroPosition and 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, updates tableHeader, and calls updateButtonSeatStates to mark raise positions.

    • Single Raise Position: Directly sets raisedPosition if 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 raisedPosition and updates seat status to Raised3Bet.

    • Marking Other Positions: Marks non-selected positions as Folded unless they are FoldedDisabled.

    • Showing Table Button: Enables the table button by setting showTableButton to true.

  • Reset Action Handling:

    • Updates the resetAction mutable state with a new reset function passed through the onResetActionChange callback.

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 to SeatStatus.Disabled and others to SeatStatus.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 Facing4BetData for data, navController for navigation, and sharedViewModel for state management.

OptionsCarousel Component:

  • Allows the user to select table size.

  • Updates the number of seats in sharedViewModel and calls tableSizeChanged to 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 seatButtonClick to update UI.

    • Available Raises Collection: Collects positions eligible for raising.

Step 1: Choosing Hero Position:

  • Index Handling: positionIndex starts at 3 and iterates through all seats.

  • Hero Position: Sets heroPosition and updates seat status based on the index.

  • Preceding Positions: Marks positions before the hero as UnSet and collects available raises.

  • State Transition: Updates currentStep, shows the arrow, updates tableHeader, and calls updateButtonSeatStates to mark raise positions.

  • Single Raise Position: Directly sets raisedPosition if 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 raisedPosition and updates seat status to RaisesAnd4Bet.

  • Marking Other Positions: Marks non-selected positions as Folded unless they are Disabled or FoldedDisabled.

  • Showing Table Button: Enables the table button by setting showTableButton to true.

Reset Action Handling:

  • Updates the resetAction mutable state with a new reset function passed through the onResetActionChange callback.

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 to SeatStatus.Disabled and others to SeatStatus.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 FacingRaiseData for data, navController for navigation, and sharedViewModel for state management.

OptionsCarousel Component:

  • Allows the user to select table size.

  • Updates the number of seats in sharedViewModel and calls tableSizeChanged to 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 seatButtonClick to update UI.

    • Available Raises Collection: Collects positions eligible for raising.

Step 1: Choosing Hero Position:

  • Index Handling: positionIndex starts at 3 and iterates through all seats.

  • Hero Position: Sets heroPosition and updates seat status based on the index.

  • Preceding Positions: Marks positions before the hero as UnSet and collects available raises.

  • State Transition: Updates currentStep, shows the arrow, updates tableHeader, and calls updateButtonSeatStates to mark raise positions.

  • Single Raise Position: Directly sets raisedPosition if 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 raisedPosition and updates seat status to RaisedPot or RaisedHalfPot based on the index.

  • Marking Other Positions: Marks non-selected positions as Folded unless they are Disabled.

  • Showing Table Button: Enables the table button by setting showTableButton to true.

Reset Action Handling:

  • Updates the resetAction mutable state with a new reset function passed through the onResetActionChange callback.

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 to SeatStatus.UnSet and others to SeatStatus.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 tableSizeChanged to 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 seatButtonClick to update UI.

    • Available Raises Collection: Collects positions eligible for raising.

Step 1: Choosing Hero Position:

  • Index Handling: positionIndex starts at 3 and iterates through all seats.

  • Hero Position: Sets heroPosition and updates seat status based on the index.

  • Preceding Positions: Marks positions before the hero as UnSet and collects available raises.

  • State Transition: Updates currentStep, shows the arrow, updates tableHeader, and calls updateButtonSeatStates to mark raise positions.

  • Single Raise Position: Directly sets raisedPosition if 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 raisedPosition and updates seat status toRaisedPot or RaisedHalfPot based on the index.

  • Computing Possible Calls: Collects positions eligible for calling after a raise.

  • Setting Folded Positions: Marks non-selected positions asFoldedDisabled unless 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 callPosition and updates seat status to Called.

  • Marking Other Positions: Marks non-selected positions as Folded unless they are Disabled.

Reset Action Handling:

  • Updates the resetAction mutable state with a new reset function passed through the onResetActionChange callback.

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 SeatStatus to be assigned if there is only one button in the list.

  • sharedViewModel: An instance of the shared SharedViewModel.

Function Logic

  1. Single Button Update:

    • If buttonsList contains only one element, the corresponding seat status is set to singleStatus.

    • sharedViewModel.showTableButton is set to true, indicating that the table button should be displayed.

  2. Multiple Buttons Update:

    • If buttonsList contains multiple elements, the corresponding seat statuses are set to SeatStatus.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

  1. Clear Existing States:

    • Clears the resetPosition and seatStatuses lists to remove any previous states.

  2. Initialize New States:

    • Iterates over the range from 0 to tableSize.

    • For each index:

      • If the index is in enabledIndices, it adds SeatStatus.UnSet to seatStatuses and the index to resetPosition.

      • Otherwise, it adds SeatStatus.Disabled to seatStatuses.

  3. Set Reset Action:

    • Updates the resetAction with the new resetPosition converted 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 SharedViewModel to 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 LazyListState representing 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

  1. Draw Content:

    • Calls drawContent to render the underlying content.

  2. 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.

  3. 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 null if an error occurs.

Function Logic

  1. Open InputStream:

    • Opens an input stream to the specified JSON file in the assets directory.

  2. Read File Content:

    • Reads the content of the file into a byte array.

    • Converts the byte array to a string using UTF-8 encoding.

  3. Handle Errors:

    • Catches IOException and 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 scrollbar function uses the composed modifier 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 scrollbar function relies on the LazyListState to 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 readJsonFromAsset function 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.