diff --git a/app/src/main/java/com/cornellappdev/score/components/EmptyState.kt b/app/src/main/java/com/cornellappdev/score/components/EmptyState.kt index 550efcd..7e440a5 100644 --- a/app/src/main/java/com/cornellappdev/score/components/EmptyState.kt +++ b/app/src/main/java/com/cornellappdev/score/components/EmptyState.kt @@ -2,6 +2,7 @@ package com.cornellappdev.score.components import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -12,6 +13,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.cornellappdev.score.R import com.cornellappdev.score.theme.GrayMedium @@ -21,7 +23,9 @@ import com.cornellappdev.score.theme.Style.heading2 @Composable fun EmptyState( - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + title: String = "No games yet.", + subtitle: String = "Check back here later!" ) { Column( modifier = modifier, @@ -34,19 +38,41 @@ fun EmptyState( ) Spacer(modifier = Modifier.height(16.dp)) Text( - text = "No games yet.", + text = title, style = heading2.copy(color = GrayPrimary) ) Spacer(modifier = Modifier.height(8.dp)) Text( - text = "Check back here later!", + text = subtitle, style = bodyNormal.copy(color = GrayMedium) ) } } +@Composable +fun EmptyStateBox( + modifier: Modifier = Modifier, + height: Dp = 550.dp, + title: String = "No games yet.", + subtitle: String = "Check back here later!" +) { + Box( + modifier = modifier + .height(height) + .fillMaxWidth(), contentAlignment = Alignment.Center + ) { + EmptyState(title = title, subtitle = subtitle) + } +} + @Preview @Composable private fun EmptyStatePreview() = ScorePreview { EmptyState() +} + +@Preview +@Composable +private fun EmptyStateBoxPreview() = ScorePreview { + EmptyStateBox() } \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/score/components/ScoreBox.kt b/app/src/main/java/com/cornellappdev/score/components/ScoreBox.kt index cfd502d..088e0f6 100644 --- a/app/src/main/java/com/cornellappdev/score/components/ScoreBox.kt +++ b/app/src/main/java/com/cornellappdev/score/components/ScoreBox.kt @@ -39,8 +39,7 @@ import com.cornellappdev.score.util.mediumGameData fun BoxScore(gameData: GameData) { val maxPeriods = maxOf( gameData.teamScores.first.scoresByPeriod.size, - gameData.teamScores.second.scoresByPeriod.size, - 4 + gameData.teamScores.second.scoresByPeriod.size ) val rowTextStyle = if (maxPeriods > 4) labelsNormal else bodyNormal Column( @@ -83,6 +82,7 @@ fun BoxScore(gameData: GameData) { TeamScoreRow( teamScore = gameData.teamScores.first, totalTextColor = saturatedGreen, + maxPeriods, rowTextStyle ) HorizontalDivider(thickness = 1.dp, color = CrimsonPrimary) @@ -90,6 +90,7 @@ fun BoxScore(gameData: GameData) { TeamScoreRow( teamScore = gameData.teamScores.second, totalTextColor = GrayMedium, + maxPeriods, rowTextStyle ) @@ -97,7 +98,12 @@ fun BoxScore(gameData: GameData) { } @Composable -fun TeamScoreRow(teamScore: TeamScore, totalTextColor: Color, rowTextStyle: TextStyle) { +fun TeamScoreRow( + teamScore: TeamScore, + totalTextColor: Color, + maxPeriods: Int, + rowTextStyle: TextStyle +) { val showEmpty = teamScore.scoresByPeriod.isEmpty() Row( @@ -127,7 +133,7 @@ fun TeamScoreRow(teamScore: TeamScore, totalTextColor: Color, rowTextStyle: Text ) } - repeat(4 - teamScore.scoresByPeriod.size) { + repeat(maxPeriods - teamScore.scoresByPeriod.size) { Text( text = "-", modifier = Modifier.weight(1f), @@ -177,5 +183,5 @@ private fun PreviewBoxScoreEmpty() = ScorePreview { @Preview @Composable private fun PreviewTeamScoreRow() = ScorePreview { - TeamScoreRow(gameData.teamScores.first, GrayMedium, bodyNormal) + TeamScoreRow(gameData.teamScores.first, GrayMedium, 4, bodyNormal) } \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/score/model/Game.kt b/app/src/main/java/com/cornellappdev/score/model/Game.kt index 4cdc40b..18ed1a2 100644 --- a/app/src/main/java/com/cornellappdev/score/model/Game.kt +++ b/app/src/main/java/com/cornellappdev/score/model/Game.kt @@ -10,6 +10,7 @@ import com.cornellappdev.score.util.parseDateOrNull import com.cornellappdev.score.util.parseDateTimeOrNull import com.cornellappdev.score.util.parseResultScore import com.cornellappdev.score.util.toGameData +import kotlinx.serialization.Serializable import java.time.LocalDate import java.time.LocalDateTime @@ -151,6 +152,7 @@ data class GameData( * @property score Current score after the event (e.g., "10 - 7"). * @property description Optional detailed description of the event. */ +@Serializable data class ScoreEvent( val id: Int, val time: String, @@ -169,6 +171,7 @@ data class TeamBoxScore( val name: String ) +@Serializable data class TeamGameSummary( val name: String, val logo: String @@ -273,12 +276,16 @@ fun List.toScoreEvents(teamLogo: String): List val teamName = boxScore.team ?: "" val corScore = boxScore.corScore ?: 0 val oppScore = boxScore.oppScore ?: 0 - + val teamSummary = if (teamName == "COR") { + TeamGameSummary(teamName, logo = R.drawable.cornell_logo.toString()) + } else { + TeamGameSummary(teamName, logo = teamLogo) + } ScoreEvent( id = index, time = boxScore.time ?: "", quarter = boxScore.period ?: "", - team = TeamGameSummary(teamName, logo = teamLogo), + team = teamSummary, eventType = "Score", // TODO: Change to what ios has and not figma score = "$corScore - $oppScore", description = boxScore.description diff --git a/app/src/main/java/com/cornellappdev/score/nav/ScoreNavHost.kt b/app/src/main/java/com/cornellappdev/score/nav/ScoreNavHost.kt index fc0e267..4818798 100644 --- a/app/src/main/java/com/cornellappdev/score/nav/ScoreNavHost.kt +++ b/app/src/main/java/com/cornellappdev/score/nav/ScoreNavHost.kt @@ -1,16 +1,21 @@ package com.cornellappdev.score.nav +import GameScoreSummaryScreenDetail import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable +import androidx.navigation.toRoute +import com.cornellappdev.score.model.ScoreEvent import com.cornellappdev.score.nav.root.ScoreScreens import com.cornellappdev.score.nav.root.ScoreScreens.Home import com.cornellappdev.score.screen.GameDetailsScreen import com.cornellappdev.score.screen.HomeScreen import com.cornellappdev.score.screen.PastGamesScreen +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.json.Json @Composable fun ScoreNavHost(navController: NavHostController) { @@ -38,9 +43,27 @@ fun ScoreNavHost(navController: NavHostController) { } } composable { - GameDetailsScreen(onBackArrow = { + GameDetailsScreen( + onBackArrow = { + navController.navigateUp() + }, + navigateToGameScoreSummary = { scoreEvents: List -> + val scoreEventsJson = + Json.encodeToString(ListSerializer(ScoreEvent.serializer()), scoreEvents) + navController.navigate(ScoreScreens.GameScoreSummaryPage(scoreEventsJson)) + } + ) + } + + composable { backStackEntry -> + val route = backStackEntry.toRoute() + val scoreEvents: List = Json.decodeFromString(route.scoreEvents) + GameScoreSummaryScreenDetail(scoreEvents = scoreEvents, onBackArrow = { navController.navigateUp() }) } + + } -} \ No newline at end of file +} + diff --git a/app/src/main/java/com/cornellappdev/score/nav/root/RootNavigation.kt b/app/src/main/java/com/cornellappdev/score/nav/root/RootNavigation.kt index 3475ef4..362268f 100644 --- a/app/src/main/java/com/cornellappdev/score/nav/root/RootNavigation.kt +++ b/app/src/main/java/com/cornellappdev/score/nav/root/RootNavigation.kt @@ -21,9 +21,8 @@ import com.cornellappdev.score.R import com.cornellappdev.score.nav.ScoreNavHost import com.cornellappdev.score.nav.ScoreNavigationBar import com.cornellappdev.score.nav.root.ScoreScreens.GameDetailsPage -import com.cornellappdev.score.nav.root.ScoreScreens.Home -import com.cornellappdev.score.nav.root.ScoreScreens.ScoresScreen import com.cornellappdev.score.theme.LocalInfiniteLoading +import com.cornellappdev.score.theme.White import kotlinx.serialization.Serializable @Composable @@ -57,12 +56,14 @@ fun RootNavigation( } - Scaffold(modifier = Modifier.fillMaxSize(), bottomBar = { - if (navBackStackEntry?.toScreen() is GameDetailsPage) { - return@Scaffold - } - ScoreNavigationBar({ navController.navigate(it) }, navBackStackEntry) - } + Scaffold( + modifier = Modifier.fillMaxSize(), bottomBar = { + if (navBackStackEntry?.toScreen() is GameDetailsPage) { + return@Scaffold + } + ScoreNavigationBar({ navController.navigate(it) }, navBackStackEntry) + }, + containerColor = White ) { innerPadding -> Box(modifier = Modifier.padding(innerPadding)) { CompositionLocalProvider(LocalInfiniteLoading provides animatedValue) { @@ -84,13 +85,17 @@ sealed class ScoreScreens { @Serializable data object ScoresScreen : ScoreScreens() + + @Serializable + data class GameScoreSummaryPage(val scoreEvents: String) : ScoreScreens() } fun NavBackStackEntry.toScreen(): ScoreScreens? = when (destination.route?.substringAfterLast(".")?.substringBefore("/")) { - "Home" -> toRoute() - "GameDetailsPage" -> toRoute() - "ScoresScreen" -> toRoute() + "Home" -> toRoute() + "GameDetailsPage" -> toRoute() + "ScoresScreen" -> toRoute() + "GameScoreSummaryPage" -> toRoute() else -> throw IllegalArgumentException("Invalid screen") } diff --git a/app/src/main/java/com/cornellappdev/score/screen/GameDetailsScreen.kt b/app/src/main/java/com/cornellappdev/score/screen/GameDetailsScreen.kt index 1989136..d2f01bf 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/GameDetailsScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/GameDetailsScreen.kt @@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -29,7 +30,7 @@ import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel import com.cornellappdev.score.R import com.cornellappdev.score.components.BoxScore -import com.cornellappdev.score.components.ButtonPrimary +import com.cornellappdev.score.components.EmptyStateBox import com.cornellappdev.score.components.ErrorState import com.cornellappdev.score.components.GameDetailsLoadingScreen import com.cornellappdev.score.components.GameScoreHeader @@ -51,15 +52,14 @@ import com.cornellappdev.score.theme.Style.heading1 import com.cornellappdev.score.theme.Style.heading2 import com.cornellappdev.score.theme.Style.heading3 import com.cornellappdev.score.theme.White -import com.cornellappdev.score.util.addToCalendar -import com.cornellappdev.score.util.toCalendarEvent import com.cornellappdev.score.viewmodel.GameDetailsViewModel import java.time.LocalDate @Composable fun GameDetailsScreen( gameDetailsViewModel: GameDetailsViewModel = hiltViewModel(), - onBackArrow: () -> Unit = {} + onBackArrow: () -> Unit = {}, + navigateToGameScoreSummary: (List) -> Unit ) { val uiState = gameDetailsViewModel.collectUiStateValue() ScorePullToRefreshBox( @@ -92,7 +92,8 @@ fun GameDetailsScreen( is ApiResponse.Success -> { GameDetailsContent( - gameCard = state.data + gameCard = state.data, + navigateToGameScoreSummary = navigateToGameScoreSummary ) } } @@ -101,7 +102,10 @@ fun GameDetailsScreen( } @Composable -fun GameDetailsContent(gameCard: DetailsCardData) { +fun GameDetailsContent( + gameCard: DetailsCardData, + navigateToGameScoreSummary: (List) -> Unit +) { Column( modifier = Modifier .background(White) @@ -163,15 +167,33 @@ fun GameDetailsContent(gameCard: DetailsCardData) { BoxScore(gameCard.gameData) Spacer(modifier = Modifier.height(24.dp)) // } - if (gameCard.boxScore.isNotEmpty()) { + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + ) { Text( "Scoring Summary", fontSize = 18.sp, style = heading2, - ) // TODO: NAVIGATION + ) + if (gameCard.boxScore.isNotEmpty()) { + Spacer(modifier = Modifier.weight(1f)) + IconButton(onClick = { navigateToGameScoreSummary(gameCard.scoreEvent) }) { + Icon( + painter = painterResource(id = R.drawable.ic_right_chevron), + contentDescription = "Back button", + modifier = Modifier + .width(24.dp) + .height(24.dp), + ) + } + } + } + if (gameCard.boxScore.isNotEmpty()) { Spacer(modifier = Modifier.height(16.dp)) ScoringSummary(gameCard.scoreEvent) } else { - Text("No Scoring Summary") // TODO: Make state when there are no scores + EmptyStateBox(height = 200.dp, title = "No scores yet.") } } else { val context = LocalContext.current @@ -190,15 +212,15 @@ fun GameDetailsContent(gameCard: DetailsCardData) { Spacer(modifier = Modifier.weight(1f)) - ButtonPrimary( - "Add to Calendar", - painterResource(R.drawable.ic_calendar), - onClick = { - gameCard.toCalendarEvent()?.let { event -> - addToCalendar(context = context, event) - } - } - ) +// ButtonPrimary( +// "Add to Calendar", +// painterResource(R.drawable.ic_calendar), +// onClick = { +// gameCard.toCalendarEvent()?.let { event -> +// addToCalendar(context = context, event) +// } +// } +// ) } } @@ -301,6 +323,6 @@ private fun GameDetailsPreview() { hoursUntilGame = 144, homeScore = 78, oppScore = 75 - ) + ), navigateToGameScoreSummary = {} ) } diff --git a/app/src/main/java/com/cornellappdev/score/screen/GameScoreSummaryScreen.kt b/app/src/main/java/com/cornellappdev/score/screen/GameScoreSummaryScreen.kt index b1230b5..0c94504 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/GameScoreSummaryScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/GameScoreSummaryScreen.kt @@ -22,21 +22,20 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.withStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import coil3.compose.AsyncImage +import com.cornellappdev.score.R import com.cornellappdev.score.components.NavigationHeader import com.cornellappdev.score.components.ScorePreview import com.cornellappdev.score.model.ScoreEvent import com.cornellappdev.score.theme.Style.bodyNormal import com.cornellappdev.score.theme.Style.spanBodyNormal -import com.cornellappdev.score.theme.White import com.cornellappdev.score.util.scoreEvents2 -import androidx.compose.foundation.layout.fillMaxSize -import coil3.compose.AsyncImage @Composable -fun GameScoreSummaryScreenDetail(scoreEvents: List) { +fun GameScoreSummaryScreenDetail(scoreEvents: List, onBackArrow: () -> Unit) { Column(modifier = Modifier.fillMaxSize()) { // TODO: add navigation - NavigationHeader(title = "Scoring Summary", {}) + NavigationHeader(title = "Scoring Summary", onBackArrow) LazyColumn( modifier = Modifier .fillMaxWidth() @@ -59,11 +58,21 @@ fun ScoreEventItemDetailed(event: ScoreEvent) { verticalAlignment = Alignment.Top, horizontalArrangement = Arrangement.SpaceBetween ) { - AsyncImage( - model = event.team.logo, - contentDescription = event.team.name, - modifier = Modifier.size(40.dp) - ) + if (event.team.name == "COR") { + Image( + painter = painterResource(R.drawable.cornell_logo), + contentDescription = event.team.name, + modifier = Modifier + .size(40.dp) + .padding(end = 12.dp) + ) + } else { + AsyncImage( + model = event.team.logo, + contentDescription = event.team.name, + modifier = Modifier.size(40.dp) + ) + } Spacer(modifier = Modifier.width(8.dp)) Column( modifier = Modifier @@ -113,5 +122,5 @@ fun ScoreEventItemDetailed(event: ScoreEvent) { @Preview @Composable private fun PreviewScoringDetailsScreen() = ScorePreview { - GameScoreSummaryScreenDetail(scoreEvents = scoreEvents2) + GameScoreSummaryScreenDetail(scoreEvents = scoreEvents2, onBackArrow = {}) } diff --git a/app/src/main/java/com/cornellappdev/score/screen/HomeScreen.kt b/app/src/main/java/com/cornellappdev/score/screen/HomeScreen.kt index ade877f..9e42a06 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/HomeScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/HomeScreen.kt @@ -24,7 +24,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel -import com.cornellappdev.score.components.EmptyState +import com.cornellappdev.score.components.EmptyStateBox import com.cornellappdev.score.components.ErrorState import com.cornellappdev.score.components.GameCard import com.cornellappdev.score.components.GamesCarousel @@ -175,12 +175,7 @@ private fun HomeLazyColumn( } } else { item { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - EmptyState() - } + EmptyStateBox() } } } diff --git a/app/src/main/java/com/cornellappdev/score/screen/PastGamesScreen.kt b/app/src/main/java/com/cornellappdev/score/screen/PastGamesScreen.kt index b831741..ce4014f 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/PastGamesScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/PastGamesScreen.kt @@ -23,6 +23,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.cornellappdev.score.components.EmptyState +import com.cornellappdev.score.components.EmptyStateBox import com.cornellappdev.score.components.ErrorState import com.cornellappdev.score.components.GamesCarousel import com.cornellappdev.score.components.LoadingScreen @@ -164,14 +165,9 @@ private fun PastGamesLazyColumn( Spacer(modifier = Modifier.height(16.dp)) } } - } else{ - item{ - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - EmptyState() - } + } else { + item { + EmptyStateBox() } } }