Skip to content
Merged
3 changes: 3 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ dependencies {
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.compose.material3)
implementation(libs.kotlinx.collections.immutable)

debugImplementation(platform(libs.androidx.compose.bom))
debugImplementation(libs.androidx.compose.ui.tooling)

lintChecks(libs.compose.lint.checks)
}
33 changes: 18 additions & 15 deletions app/src/main/kotlin/com/juandiana/reflexgame/ui/MainScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.juandiana.reflexgame.R
import com.juandiana.reflexgame.ui.theme.ExtendedReflexGameTheme
import com.juandiana.reflexgame.ui.theme.ReflexGameTheme
import com.juandiana.reflexgame.ui.theme.spacing
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import java.util.Locale

@Composable
Expand All @@ -51,19 +53,20 @@ fun MainScreenRoute(viewModel: MainViewModel) {
@Composable
fun MainScreen(
showTitle: Boolean,
squares: List<SquareState>,
squares: ImmutableList<SquareState>,
scoreText: String,
buttonText: String,
elapsedTimeText: String,
columnCount: Int,
onSquareTouched: (Int) -> Unit,
onButtonClicked: () -> Unit
onButtonClicked: () -> Unit,
modifier: Modifier = Modifier
) {
Surface(modifier = Modifier.fillMaxSize()) {
Surface(modifier = modifier.fillMaxSize()) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(MaterialTheme.spacing.medium)
.padding(ExtendedReflexGameTheme.spacing.medium)
) {
Text(
modifier = Modifier
Expand All @@ -72,20 +75,20 @@ fun MainScreen(
text = stringResource(id = R.string.game_over).uppercase(Locale.getDefault()),
style = MaterialTheme.typography.displayLarge
)
Spacer(modifier = Modifier.height(MaterialTheme.spacing.medium))
Spacer(modifier = Modifier.height(ExtendedReflexGameTheme.spacing.medium))
SquaresGrid(
modifier = Modifier.fillMaxWidth(),
squares = squares,
columnCount = columnCount,
onSquareTouched = onSquareTouched
)
Spacer(modifier = Modifier.height(MaterialTheme.spacing.medium))
Spacer(modifier = Modifier.height(ExtendedReflexGameTheme.spacing.medium))
HeadsUpDisplay(
modifier = Modifier.fillMaxWidth(),
scoreText = scoreText,
elapsedTimeText = elapsedTimeText
)
Spacer(modifier = Modifier.height(MaterialTheme.spacing.medium))
Spacer(modifier = Modifier.height(ExtendedReflexGameTheme.spacing.medium))
Button(
onClick = onButtonClicked,
modifier = Modifier.fillMaxWidth()
Expand All @@ -101,15 +104,15 @@ fun MainScreen(

@Composable
private fun SquaresGrid(
squares: List<SquareState>,
squares: ImmutableList<SquareState>,
columnCount: Int,
onSquareTouched: (Int) -> Unit,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
.background(Color.Black)
.padding(MaterialTheme.spacing.border)
.padding(ExtendedReflexGameTheme.spacing.border)
) {
squares.chunked(columnCount).forEachIndexed { rowIndex, rowSquares ->
Row(modifier = Modifier.fillMaxWidth()) {
Expand Down Expand Up @@ -137,7 +140,7 @@ private fun SquaresGrid(
private fun HeadsUpDisplay(
scoreText: String,
elapsedTimeText: String,
modifier: Modifier
modifier: Modifier = Modifier
) {
Row(
modifier = modifier,
Expand All @@ -147,7 +150,7 @@ private fun HeadsUpDisplay(
text = stringResource(id = R.string.score),
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.width(MaterialTheme.spacing.small))
Spacer(modifier = Modifier.width(ExtendedReflexGameTheme.spacing.small))
Text(
modifier = Modifier.weight(1f),
text = scoreText
Expand All @@ -156,7 +159,7 @@ private fun HeadsUpDisplay(
text = stringResource(id = R.string.time),
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.width(MaterialTheme.spacing.small))
Spacer(modifier = Modifier.width(ExtendedReflexGameTheme.spacing.small))
Text(text = elapsedTimeText)
}
}
Expand All @@ -167,7 +170,7 @@ private fun SquareItem(
onSquareTouched: () -> Unit,
modifier: Modifier = Modifier
) {
val border = MaterialTheme.spacing.border
val border = ExtendedReflexGameTheme.spacing.border
val color = when (squareState) {
SquareState.CLEAR -> Color.White
SquareState.FLASHED -> Color.Blue
Expand Down Expand Up @@ -198,7 +201,7 @@ private fun MainScreenPreview() {
ReflexGameTheme {
MainScreen(
showTitle = false,
squares = listOf(
squares = persistentListOf(
SquareState.CLEAR,
SquareState.FLASHED,
SquareState.CLEAR,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.juandiana.reflexgame.ui

import kotlinx.collections.immutable.ImmutableList

enum class SquareState {
CLEAR,
FLASHED,
Expand All @@ -9,7 +11,7 @@ enum class SquareState {

data class MainScreenState(
val showTitle: Boolean,
val squares: List<SquareState>,
val squares: ImmutableList<SquareState>,
val scoreText: String,
val buttonText: String,
val elapsedTimeText: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList

class MainViewModel(
private val resourcesProvider: ResourcesProvider,
Expand Down Expand Up @@ -99,7 +101,7 @@ class MainViewModel(
} else {
SquareState.CLEAR
}
}
}.toImmutableList()
)
}

Expand All @@ -117,7 +119,7 @@ class MainViewModel(
} else {
SquareState.CLEAR
}
}
}.toImmutableList()
)
}

Expand All @@ -135,7 +137,8 @@ class MainViewModel(
}
}

private fun clearSquares(): List<SquareState> = List(gameEngine.squaresCount) { SquareState.CLEAR }
private fun clearSquares(): ImmutableList<SquareState> =
List(gameEngine.squaresCount) { SquareState.CLEAR }.toImmutableList()

private fun elapsedSecondsSince(startTime: Long): Long {
return ((SystemClock.elapsedRealtime() - startTime) / 1000L).coerceAtLeast(0L)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.juandiana.reflexgame.ui.theme

import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import android.annotation.SuppressLint
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.unit.Dp
Expand All @@ -14,8 +13,5 @@ internal data class ReflexGameSpacing(
val medium: Dp = 16.dp
)

@SuppressLint("ComposeCompositionLocalUsage")
internal val LocalReflexGameSpacing = staticCompositionLocalOf { ReflexGameSpacing() }

internal val MaterialTheme.spacing: ReflexGameSpacing
@Composable
get() = LocalReflexGameSpacing.current
19 changes: 11 additions & 8 deletions app/src/main/kotlin/com/juandiana/reflexgame/ui/theme/Theme.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,18 @@ private val ReflexGameLightColorScheme = lightColorScheme(
onSecondary = Black
)

internal object ExtendedReflexGameTheme {
val spacing: ReflexGameSpacing
@Composable
get() = LocalReflexGameSpacing.current
}

@Composable
internal fun ReflexGameTheme(content: @Composable () -> Unit) {
MaterialTheme(
colorScheme = ReflexGameLightColorScheme,
typography = ReflexGameTypography
) {
CompositionLocalProvider(
LocalReflexGameSpacing provides ReflexGameSpacing(),
content = content
)
CompositionLocalProvider(LocalReflexGameSpacing provides ReflexGameSpacing()) {
MaterialTheme(
colorScheme = ReflexGameLightColorScheme,
typography = ReflexGameTypography
) { content() }
}
}
4 changes: 4 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
[versions]
android-gradle-plugin = "9.0.1"
kotlin = "2.3.10"
kotlinx-collections-immutable = "0.4.0"
androidx-activity = "1.12.4"
androidx-lifecycle = "2.10.0"
androidx-compose-bom = "2026.02.00"
compose-lints = "1.4.2"

[plugins]
android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }

[libraries]
kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinx-collections-immutable" }
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" }
androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidx-lifecycle" }
androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }
Expand All @@ -18,3 +21,4 @@ androidx-compose-ui = { module = "androidx.compose.ui:ui" }
androidx-compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
androidx-compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" }
androidx-compose-material3 = { module = "androidx.compose.material3:material3" }
compose-lint-checks = { module = "com.slack.lint.compose:compose-lint-checks", version.ref = "compose-lints" }