Created
April 9, 2026 15:23
-
-
Save guiathayde/5b2500d81480aff58413e87b085b0af4 to your computer and use it in GitHub Desktop.
Basket Score Kotlin Android
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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