Skip to content

Instantly share code, notes, and snippets.

@guiathayde
Created April 9, 2026 15:23
Show Gist options
  • Select an option

  • Save guiathayde/5b2500d81480aff58413e87b085b0af4 to your computer and use it in GitHub Desktop.

Select an option

Save guiathayde/5b2500d81480aff58413e87b085b0af4 to your computer and use it in GitHub Desktop.
Basket Score Kotlin Android
package com.guiathayde.basketscorekotlin
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
data class Play(val team: Int, val points: Int)
class BasketScoreViewModel : ViewModel() {
var team1Score by mutableIntStateOf(0)
private set
var team2Score by mutableIntStateOf(0)
private set
var lastPlay by mutableStateOf<Play?>(null)
private set
val canUndo: Boolean
get() = lastPlay != null
fun addPoints(team: Int, points: Int) {
if (team == 1) {
team1Score += points
} else {
team2Score += points
}
lastPlay = Play(team, points)
}
fun reset() {
team1Score = 0
team2Score = 0
lastPlay = null
}
fun undoLastPlay() {
lastPlay?.let { play ->
if (play.team == 1) {
team1Score -= play.points
} else {
team2Score -= play.points
}
lastPlay = null
}
}
}
package com.guiathayde.basketscorekotlin
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.guiathayde.basketscorekotlin.ui.theme.BasketScoreKotlinTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
BasketScoreKotlinTheme {
BasketScoreApp()
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BasketScoreApp(viewModel: BasketScoreViewModel = viewModel()) {
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
TopAppBar(
title = {
Text(
text = stringResource(R.string.app_name),
fontWeight = FontWeight.Bold
)
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primary,
titleContentColor = MaterialTheme.colorScheme.onPrimary
)
)
}
) { innerPadding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.SpaceBetween
) {
Spacer(modifier = Modifier.height(16.dp))
ScoreDisplay(
team1Score = viewModel.team1Score,
team2Score = viewModel.team2Score
)
Spacer(modifier = Modifier.height(24.dp))
TeamButtonsRow(
onTeam1Points = { points -> viewModel.addPoints(1, points) },
onTeam2Points = { points -> viewModel.addPoints(2, points) }
)
Spacer(modifier = Modifier.height(24.dp))
OutlinedButton(
onClick = { viewModel.undoLastPlay() },
enabled = viewModel.canUndo,
modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.outlinedButtonColors(
contentColor = MaterialTheme.colorScheme.error
)
) {
Text(
text = stringResource(R.string.undo_play),
fontSize = 16.sp
)
}
Spacer(modifier = Modifier.height(8.dp))
Button(
onClick = { viewModel.reset() },
modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.error
)
) {
Text(
text = stringResource(R.string.reset),
fontSize = 16.sp
)
}
Spacer(modifier = Modifier.height(16.dp))
}
}
}
@Composable
fun ScoreDisplay(team1Score: Int, team2Score: Int) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Text(
text = stringResource(R.string.team_1),
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.primary
)
Text(
text = stringResource(R.string.team_2),
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.tertiary
)
}
Spacer(modifier = Modifier.height(8.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
AnimatedScore(score = team1Score, color = MaterialTheme.colorScheme.primary)
Text(
text = " x ",
fontSize = 40.sp,
fontWeight = FontWeight.Light,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
AnimatedScore(score = team2Score, color = MaterialTheme.colorScheme.tertiary)
}
}
}
@Composable
fun AnimatedScore(score: Int, color: androidx.compose.ui.graphics.Color) {
AnimatedContent(
targetState = score,
transitionSpec = {
if (targetState > initialState) {
(slideInVertically { -it } + fadeIn()) togetherWith
(slideOutVertically { it } + fadeOut())
} else {
(slideInVertically { it } + fadeIn()) togetherWith
(slideOutVertically { -it } + fadeOut())
}
},
label = "scoreAnimation"
) { targetScore ->
Text(
text = "$targetScore",
fontSize = 72.sp,
fontWeight = FontWeight.Bold,
color = color,
textAlign = TextAlign.Center,
modifier = Modifier.width(120.dp)
)
}
}
@Composable
fun TeamButtonsRow(
onTeam1Points: (Int) -> Unit,
onTeam2Points: (Int) -> Unit
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
TeamButtons(
teamName = stringResource(R.string.team_1),
onPoints = onTeam1Points,
buttonColors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary
),
modifier = Modifier.weight(1f)
)
TeamButtons(
teamName = stringResource(R.string.team_2),
onPoints = onTeam2Points,
buttonColors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.tertiary
),
modifier = Modifier.weight(1f)
)
}
}
@Composable
fun TeamButtons(
teamName: String,
onPoints: (Int) -> Unit,
buttonColors: ButtonColors,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(
onClick = { onPoints(1) },
modifier = Modifier.fillMaxWidth(),
colors = buttonColors
) {
Text(stringResource(R.string.free_throw))
}
Button(
onClick = { onPoints(2) },
modifier = Modifier.fillMaxWidth(),
colors = buttonColors
) {
Text(stringResource(R.string.two_points))
}
Button(
onClick = { onPoints(3) },
modifier = Modifier.fillMaxWidth(),
colors = buttonColors
) {
Text(stringResource(R.string.three_points))
}
}
}
@Preview(showBackground = true)
@Composable
fun BasketScorePreview() {
BasketScoreKotlinTheme {
BasketScoreApp()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment