From 826118fb38e18426831e67df8b9e9b5f55422479 Mon Sep 17 00:00:00 2001 From: Emil Jiang Date: Thu, 13 Mar 2025 11:49:54 -0400 Subject: [PATCH 01/19] gamedetails networking --- app/src/main/graphql/GameById.graphql | 32 ++++++++++ .../com/cornellappdev/score/model/Game.kt | 39 ++++++++++++ .../score/model/ScoreRepository.kt | 60 ++++++++++++++++++- 3 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 app/src/main/graphql/GameById.graphql diff --git a/app/src/main/graphql/GameById.graphql b/app/src/main/graphql/GameById.graphql new file mode 100644 index 0000000..aced4b7 --- /dev/null +++ b/app/src/main/graphql/GameById.graphql @@ -0,0 +1,32 @@ +query GameById($id: String!) { + game(id: $id){ + id + city + date + gender + location + opponentId + result + sport + state + time + scoreBreakdown + team { + id + color + image + name + } + boxScore { + team + period + time + description + scorer + assist + scoreBy + corScore + oppScore + } + } +} \ 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 7a71627..b0a8c92 100644 --- a/app/src/main/java/com/cornellappdev/score/model/Game.kt +++ b/app/src/main/java/com/cornellappdev/score/model/Game.kt @@ -15,6 +15,45 @@ data class Game( val city: String ) +/** + * Clean up? + */ +data class GameDetailsTeam( + val id: String?, + val color: String?, + val image: String?, + val name: String? +) + +data class GameDetailsBoxScore( + val team: String?, + val period: String?, + val time: String?, + val description: String?, + val scorer: String?, + val assist: String?, + val scoreBy: String?, + val corScore: Int?, + val oppScore: Int? +) + +data class GameDetailsGame( + val id: String, + val city: String?, + val date: String?, + val gender: String?, + val location: String?, + val opponentId: String?, + val result: String?, + val sport: String?, + val state: String?, + val time: String?, + val scoreBreakdown: List?>?, + val team: GameDetailsTeam?, + val boxScore: List? +) + + //Data for HomeScreen game displays data class GameCardData( val teamLogo: String, diff --git a/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt b/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt index 073c0e4..a47343b 100644 --- a/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt +++ b/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt @@ -2,10 +2,13 @@ package com.cornellappdev.score.model import android.util.Log import com.apollographql.apollo.ApolloClient +import com.example.score.GameByIdQuery import com.example.score.GamesQuery import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import javax.inject.Inject import javax.inject.Singleton @@ -26,7 +29,8 @@ class ScoreRepository @Inject constructor( MutableStateFlow>>(ApiResponse.Loading) val upcomingGamesFlow = _upcomingGamesFlow.asStateFlow() - + private val _currGameFlow = MutableStateFlow>(ApiResponse.Loading) + val currGamesFlow = _currGameFlow.asStateFlow() /** * Asynchronously fetches the list of games from the API. Once finished, will send down * `upcomingGamesFlow` to be observed. @@ -58,4 +62,58 @@ class ScoreRepository @Inject constructor( _upcomingGamesFlow.value = ApiResponse.Error } } + + fun getGameById(id: String) = appScope.launch { + _currGameFlow.value = ApiResponse.Loading + try { + val response = apolloClient.query(GameByIdQuery(id)).execute() + val game = response.data?.game + + if (game != null) { + val temp = GameDetailsGame( + id = game.id ?: "", + city = game.city, + date = game.date, + gender = game.gender, + location = game.location, + opponentId = game.opponentId, + result = game.result, + sport = game.sport, + state = game.state, + time = game.time, + scoreBreakdown = game.scoreBreakdown, + team = game.team?.let { team -> + GameDetailsTeam( + id = team.id, + color = team.color, + image = team.image, + name = team.name + ) + }, + boxScore = game.boxScore?.mapNotNull { boxScore -> + boxScore?.let { + GameDetailsBoxScore( + team = it.team, + period = it.period, + time = it.time, + description = it.description, + scorer = it.scorer, + assist = it.assist, + scoreBy = it.scoreBy, + corScore = it.corScore, + oppScore = it.oppScore + ) + } + } + ) + _currGameFlow.value = ApiResponse.Success(temp) + } else { + _currGameFlow.value = ApiResponse.Error + } + } catch (e: Exception) { + Log.e("ScoreRepository", "Error fetching game with id: ${id}: ", e) + _currGameFlow.value = ApiResponse.Error + } + } + } \ No newline at end of file From 8cbd945010ee7cf443d3b4c5453496265a1d0e47 Mon Sep 17 00:00:00 2001 From: Emil Jiang Date: Tue, 18 Mar 2025 22:57:43 -0400 Subject: [PATCH 02/19] all PR comments addressed except for toResult --- .../com/cornellappdev/score/model/Game.kt | 3 -- .../score/model/GameByIdQueryMappers.kt | 44 ++++++++++++++++ .../score/model/ScoreRepository.kt | 52 ++++--------------- 3 files changed, 54 insertions(+), 45 deletions(-) create mode 100644 app/src/main/java/com/cornellappdev/score/model/GameByIdQueryMappers.kt 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 b0a8c92..a522552 100644 --- a/app/src/main/java/com/cornellappdev/score/model/Game.kt +++ b/app/src/main/java/com/cornellappdev/score/model/Game.kt @@ -15,9 +15,6 @@ data class Game( val city: String ) -/** - * Clean up? - */ data class GameDetailsTeam( val id: String?, val color: String?, diff --git a/app/src/main/java/com/cornellappdev/score/model/GameByIdQueryMappers.kt b/app/src/main/java/com/cornellappdev/score/model/GameByIdQueryMappers.kt new file mode 100644 index 0000000..fe4213e --- /dev/null +++ b/app/src/main/java/com/cornellappdev/score/model/GameByIdQueryMappers.kt @@ -0,0 +1,44 @@ +package com.cornellappdev.score.model + +import com.example.score.GameByIdQuery + +fun GameByIdQuery.Game.toGameDetails(): GameDetailsGame { + return GameDetailsGame( + id = this.id ?: "", + city = this.city, + date = this.date, + gender = this.gender, + location = this.location, + opponentId = this.opponentId, + result = this.result, + sport = this.sport, + state = this.state, + time = this.time, + scoreBreakdown = this.scoreBreakdown, + team = this.team?.toGameDetailsTeam(), + boxScore = this.boxScore?.mapNotNull { it?.toGameDetailsBoxScore() } + ) +} +fun GameByIdQuery.Team.toGameDetailsTeam(): GameDetailsTeam { + return GameDetailsTeam( + id = this.id, + color = this.color, + image = this.image, + name = this.name + ) +} + +fun GameByIdQuery.BoxScore.toGameDetailsBoxScore(): GameDetailsBoxScore { + return GameDetailsBoxScore( + team = this.team, + period = this.period, + time = this.time, + description = this.description, + scorer = this.scorer, + assist = this.assist, + scoreBy = this.scoreBy, + corScore = this.corScore, + oppScore = this.oppScore + ) +} + diff --git a/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt b/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt index a47343b..dd9aba9 100644 --- a/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt +++ b/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt @@ -29,8 +29,8 @@ class ScoreRepository @Inject constructor( MutableStateFlow>>(ApiResponse.Loading) val upcomingGamesFlow = _upcomingGamesFlow.asStateFlow() - private val _currGameFlow = MutableStateFlow>(ApiResponse.Loading) - val currGamesFlow = _currGameFlow.asStateFlow() + private val _currentGameFlow = MutableStateFlow>(ApiResponse.Loading) + val currentGamesFlow = _currentGameFlow.asStateFlow() /** * Asynchronously fetches the list of games from the API. Once finished, will send down * `upcomingGamesFlow` to be observed. @@ -63,56 +63,24 @@ class ScoreRepository @Inject constructor( } } + /** + * Asynchronously fetches game details for a particular game. Once finished, will send down + * `currentGamesFlow` to be observed. + */ fun getGameById(id: String) = appScope.launch { - _currGameFlow.value = ApiResponse.Loading + _currentGameFlow.value = ApiResponse.Loading try { val response = apolloClient.query(GameByIdQuery(id)).execute() val game = response.data?.game if (game != null) { - val temp = GameDetailsGame( - id = game.id ?: "", - city = game.city, - date = game.date, - gender = game.gender, - location = game.location, - opponentId = game.opponentId, - result = game.result, - sport = game.sport, - state = game.state, - time = game.time, - scoreBreakdown = game.scoreBreakdown, - team = game.team?.let { team -> - GameDetailsTeam( - id = team.id, - color = team.color, - image = team.image, - name = team.name - ) - }, - boxScore = game.boxScore?.mapNotNull { boxScore -> - boxScore?.let { - GameDetailsBoxScore( - team = it.team, - period = it.period, - time = it.time, - description = it.description, - scorer = it.scorer, - assist = it.assist, - scoreBy = it.scoreBy, - corScore = it.corScore, - oppScore = it.oppScore - ) - } - } - ) - _currGameFlow.value = ApiResponse.Success(temp) + _currentGameFlow.value = ApiResponse.Success(game.toGameDetails()) } else { - _currGameFlow.value = ApiResponse.Error + _currentGameFlow.value = ApiResponse.Error } } catch (e: Exception) { Log.e("ScoreRepository", "Error fetching game with id: ${id}: ", e) - _currGameFlow.value = ApiResponse.Error + _currentGameFlow.value = ApiResponse.Error } } From cd5cf1698a3597fc69185f33b1e680bb2e1ad13d Mon Sep 17 00:00:00 2001 From: Emil Jiang Date: Wed, 19 Mar 2025 00:34:56 -0400 Subject: [PATCH 03/19] fixed data classes to match schema --- .../com/cornellappdev/score/model/Game.kt | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) 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 a522552..5ec9399 100644 --- a/app/src/main/java/com/cornellappdev/score/model/Game.kt +++ b/app/src/main/java/com/cornellappdev/score/model/Game.kt @@ -17,9 +17,9 @@ data class Game( data class GameDetailsTeam( val id: String?, - val color: String?, + val color: String, val image: String?, - val name: String? + val name: String ) data class GameDetailsBoxScore( @@ -35,15 +35,15 @@ data class GameDetailsBoxScore( ) data class GameDetailsGame( - val id: String, - val city: String?, - val date: String?, - val gender: String?, + val id: String?, + val city: String, + val date: String, + val gender: String, val location: String?, - val opponentId: String?, + val opponentId: String, val result: String?, - val sport: String?, - val state: String?, + val sport: String, + val state: String, val time: String?, val scoreBreakdown: List?>?, val team: GameDetailsTeam?, @@ -51,6 +51,7 @@ data class GameDetailsGame( ) + //Data for HomeScreen game displays data class GameCardData( val teamLogo: String, From 79459930d416578a6291c60db496c93c79dc1112 Mon Sep 17 00:00:00 2001 From: Emil Jiang Date: Wed, 19 Mar 2025 18:52:43 -0400 Subject: [PATCH 04/19] merge --- .../com/cornellappdev/score/model/Game.kt | 19 +++- .../score/screen/GameDetailsScreen.kt | 42 +++++-- .../score/screen/GameScoreSummaryScreen.kt | 1 + .../score/viewmodel/GameDetailsViewModel.kt | 107 +++++++++++++++++- 4 files changed, 155 insertions(+), 14 deletions(-) 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 786fa2d..9e3f427 100644 --- a/app/src/main/java/com/cornellappdev/score/model/Game.kt +++ b/app/src/main/java/com/cornellappdev/score/model/Game.kt @@ -55,7 +55,7 @@ data class GameDetailsGame( -//Data for HomeScreen game displays +// Data for HomeScreen game displays data class GameCardData( val teamLogo: String, val team: String, @@ -70,6 +70,23 @@ data class GameCardData( val sportIcon: Int ) +// Data for GameDetailsScreen +data class DetailsCardData( + val opponentLogo: String, + val opponent: String, + val opponentColor: Int, + val date: LocalDate?, + val dateString: String, + val isLive: Boolean, + val location: String, + val gender: String, + val genderIcon: Int, + val sport: String, + val sportIcon: Int, + val boxScore: List, + val scoreBreakdown: List> +) + // Scoring information for a specific team, used in the box score data class TeamScore( val team: Team, 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 556d02f..11921cf 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/GameDetailsScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/GameDetailsScreen.kt @@ -10,8 +10,11 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -19,10 +22,10 @@ import androidx.compose.ui.graphics.ColorFilter 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.R import com.cornellappdev.score.components.ButtonPrimary import com.cornellappdev.score.components.GameScoreHeader -import com.cornellappdev.score.components.NavigationHeader import com.cornellappdev.score.components.TimeUntilStartCard import com.cornellappdev.score.theme.GrayMedium import com.cornellappdev.score.theme.GrayPrimary @@ -30,17 +33,37 @@ import com.cornellappdev.score.theme.Style.bodyNormal import com.cornellappdev.score.theme.Style.heading1 import com.cornellappdev.score.theme.Style.heading3 import com.cornellappdev.score.theme.White +import com.cornellappdev.score.viewmodel.GameDetailsViewModel +import com.cornellappdev.score.viewmodel.HomeViewModel @Composable -fun GameDetailsScreen() { - Column(modifier = Modifier.background(White).fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally) { - // TODO: add navigation - NavigationHeader(title = "Game Details", {}) +fun GameDetailsScreen( + gameId: String, + gameDetailsViewModel: GameDetailsViewModel = hiltViewModel()) +{ + val uiState by gameDetailsViewModel.uiStateFlow.collectAsState() + Column( + modifier = Modifier + .background(White) + .fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Box(modifier = Modifier.height(95.dp)) + val gameCard = uiState.gameCard + if (gameCard == null) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator(color = GrayPrimary) + } + return + } GameScoreHeader( leftTeamLogo = painterResource(R.drawable.cornell_logo), - rightTeamLogo = painterResource(R.drawable.penn_logo), + rightTeamLogo = gameCard.team!!.image!!, gradientColor1 = Color(0xFFE1A69F), - gradientColor2 = Color(0xFF011F5B), + gradientColor2 = Color(gameCard.team.color), modifier = Modifier.height(185.dp) ) @@ -48,7 +71,7 @@ fun GameDetailsScreen() { Column(Modifier.padding(horizontal = 24.dp)) { Text( - text = "Men's Football", + text = gameCard.sport, style = heading3.copy(color = GrayPrimary) ) Text( @@ -91,11 +114,10 @@ fun GameDetailsScreen() { } } - @Preview @Composable private fun GameDetailsScreenPreview() { - GameDetailsScreen() +// GameDetailsScreen() // import androidx.compose.ui.tooling.preview.Preview // import androidx.compose.ui.unit.dp // import com.cornellappdev.score.R 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 a34d466..5d9fcc6 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/GameScoreSummaryScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/GameScoreSummaryScreen.kt @@ -3,6 +3,7 @@ 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.padding import androidx.compose.foundation.layout.size diff --git a/app/src/main/java/com/cornellappdev/score/viewmodel/GameDetailsViewModel.kt b/app/src/main/java/com/cornellappdev/score/viewmodel/GameDetailsViewModel.kt index 3526a73..4e64304 100644 --- a/app/src/main/java/com/cornellappdev/score/viewmodel/GameDetailsViewModel.kt +++ b/app/src/main/java/com/cornellappdev/score/viewmodel/GameDetailsViewModel.kt @@ -1,20 +1,121 @@ package com.cornellappdev.score.viewmodel +import android.util.Log +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.viewModelScope +import com.cornellappdev.score.R +import com.cornellappdev.score.model.ApiResponse +import com.cornellappdev.score.model.Game import com.cornellappdev.score.model.GameCardData +import com.cornellappdev.score.model.GameDetailsGame +import com.cornellappdev.score.model.ScoreRepository +import com.cornellappdev.score.model.Sport import com.cornellappdev.score.nav.root.RootNavigationRepository import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import java.time.LocalDate import javax.inject.Inject @HiltViewModel class GameDetailsViewModel @Inject constructor( private val rootNavigationRepository: RootNavigationRepository, + private val scoreRepository: ScoreRepository, + private val savedStateHandle: SavedStateHandle ) : BaseViewModel( initialUiState = GameDetailsUiState( - homeScore = 0, awayScore = 0 + gameCard = null, + isLive = false ) ) { + init { + observeCurrentGame() + viewModelScope.launch{ + val gameId: String? = savedStateHandle["gameId"] + scoreRepository.getGameById(gameId!!) + } + } + private fun observeCurrentGame() = scoreRepository.currentGamesFlow.onEach { response -> + Log.d("HomeViewModel", "Response: $response") + updateGameCard(response) + }.launchIn(viewModelScope) + + private fun updateGameCard(response: ApiResponse) { + val game: GameDetailsGame? = when (response) { + is ApiResponse.Success -> { + Log.d("updateGameList", "Success") + response.data + } + ApiResponse.Error -> { + Log.d("updateGameList", "Error") + null + } + ApiResponse.Loading -> { + Log.d("updateGameList", "Loading") + null + } + } + if (game == null){ + return + } + val currentDate = LocalDate.now() + val tomorrowDate = LocalDate.now().plusDays(1) + val formattedDate = formatDate(game.date) + + applyMutation { + copy(gameCard = gameCard) + } + } + private fun formatDate(strDate: String): LocalDate? { + val monthMap = mapOf( + "Jan" to 1, + "Feb" to 2, + "Mar" to 3, + "Apr" to 4, + "May" to 5, + "Jun" to 6, + "Jul" to 7, + "Aug" to 8, + "Sep" to 9, + "Oct" to 10, + "Nov" to 11, + "Dec" to 12 + ) + + val parts = strDate.split(" ") + if (parts.size < 2) return null + + val month = monthMap[parts[0]] + if (month == null) { + return null + } + val day = parts[1].toIntOrNull() ?: return null + + val currentYear = LocalDate.now().year + //Log.d("HomeViewModel", "formatDate: ${LocalDate.of(currentYear, month, day)}") + return LocalDate.of(currentYear, month, day) + } + + /** + * Converts from format "#xxxxxx" to a valid hex, with alpha = 40. Ready to be passed into Color() + */ + + private fun formatColor(color: String): Int { + val alpha = (40 * 255 / 100)// Convert percent to hex (0-255) + val colorInt = Integer.parseInt(color.removePrefix("#"), 16) + return (alpha shl 24) or colorInt + } + + private fun dateToString(date: LocalDate?): String { + if (date == null) { + return "--" + } + //Log.d("HomeViewModel", "formatedDate: ${date.month.value}/${date.dayOfMonth}/${date.year}") + return "${date.month.value}/${date.dayOfMonth}/${date.year}" + } data class GameDetailsUiState( - val homeScore: Int, - val awayScore: Int, + val gameCard: GameDetailsGame?, + val isLive: Boolean ) } \ No newline at end of file From 002a60cbfdd57784b723b8e95360f502893d36e3 Mon Sep 17 00:00:00 2001 From: Emil Jiang Date: Sun, 23 Mar 2025 21:35:36 -0400 Subject: [PATCH 05/19] first draft --- .../score/components/GameScoreHeader.kt | 17 +- .../score/components/ScoreSummary.kt | 30 ++- .../com/cornellappdev/score/model/Game.kt | 76 ++++++- .../score/model/GameByIdQueryMappers.kt | 3 +- .../score/model/ScoreRepository.kt | 14 +- .../score/screen/GameDetailsScreen.kt | 203 ++++++++---------- .../com/cornellappdev/score/util/DateUtil.kt | 30 +++ .../cornellappdev/score/util/GameDataUtil.kt | 47 ++++ .../score/util/TestingConstants.kt | 21 +- .../score/viewmodel/GameDetailsViewModel.kt | 122 ++--------- 10 files changed, 306 insertions(+), 257 deletions(-) create mode 100644 app/src/main/java/com/cornellappdev/score/util/GameDataUtil.kt diff --git a/app/src/main/java/com/cornellappdev/score/components/GameScoreHeader.kt b/app/src/main/java/com/cornellappdev/score/components/GameScoreHeader.kt index 5c7cf90..3c7db03 100644 --- a/app/src/main/java/com/cornellappdev/score/components/GameScoreHeader.kt +++ b/app/src/main/java/com/cornellappdev/score/components/GameScoreHeader.kt @@ -28,6 +28,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign 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.theme.Style.scoreHeaderText import com.cornellappdev.score.theme.Style.vsText @@ -35,9 +36,11 @@ import com.cornellappdev.score.theme.Style.vsText @Composable fun GameScoreHeader( leftTeamLogo: Painter, - rightTeamLogo: Painter, + rightTeamLogo: String, gradientColor1: Color, gradientColor2: Color, + leftScore: Int, + rightScore: Int, modifier: Modifier = Modifier ) { Box( @@ -64,7 +67,7 @@ fun GameScoreHeader( Row { Text( - text = "0", + text = leftScore.toString(), style = scoreHeaderText, modifier = Modifier.width(52.dp), textAlign = TextAlign.Center @@ -76,15 +79,15 @@ fun GameScoreHeader( ) Text( - text = "0", + text = rightScore.toString(), style = scoreHeaderText, modifier = Modifier.width(52.dp), textAlign = TextAlign.Center ) } - Image( - painter = rightTeamLogo, + AsyncImage( + model = rightTeamLogo, contentDescription = "Right Team Logo", modifier = Modifier.height(70.dp) ) @@ -97,9 +100,11 @@ fun GameScoreHeader( private fun GameScoreHeaderPreview() { GameScoreHeader( leftTeamLogo = painterResource(R.drawable.cornell_logo), - rightTeamLogo = painterResource(R.drawable.penn_logo), + rightTeamLogo = "https://images.sidearmdev.com/fit?url=https%3a%2f%2fdxbhsrqyrr690.cloudfront.net%2fsidearm.nextgen.sites%2fcornellbigred.com%2fimages%2flogos%2fpenn_200x200.png&height=80&width=80&type=webp", gradientColor1 = Color(0xFFE1A69F), gradientColor2 = Color(0xFF011F5B), + leftScore = 0, + rightScore = 0, modifier = Modifier.height(185.dp) ) } \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/score/components/ScoreSummary.kt b/app/src/main/java/com/cornellappdev/score/components/ScoreSummary.kt index 73d1f7e..d4aa153 100644 --- a/app/src/main/java/com/cornellappdev/score/components/ScoreSummary.kt +++ b/app/src/main/java/com/cornellappdev/score/components/ScoreSummary.kt @@ -17,6 +17,8 @@ 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 coil3.compose.AsyncImage +import com.cornellappdev.score.R import com.cornellappdev.score.model.ScoreEvent import com.cornellappdev.score.theme.GrayPrimary import com.cornellappdev.score.theme.Style.bodyMedium @@ -47,13 +49,25 @@ fun ScoreEventItem(event: ScoreEvent) { .padding(vertical = 16.dp), verticalAlignment = Alignment.CenterVertically ) { - Image( - painter = painterResource(event.team.logo), - contentDescription = event.team.name, - modifier = Modifier - .size(40.dp) - .padding(end = 12.dp) - ) + if (event.team.name == "COR"){ // TODO: Check if its "COR" for all queries. It is for baseball + 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, // Turn this into a if statement if i know the link for cornell logo + modifier = Modifier + .size(40.dp) + .padding(end = 12.dp) + ) + } + Row( modifier = Modifier.weight(2f), @@ -91,7 +105,7 @@ fun ScoreEventItem(event: ScoreEvent) { Row(verticalAlignment = Alignment.CenterVertically) { Text( text = homeScore.toString(), - style = if (event.team.name == "Cornell") metricSemibold else metricNormal, + style = if (event.team.name == "Cornell") metricSemibold else metricNormal, // TODO: Check name color = GrayPrimary, textAlign = TextAlign.Center ) 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 9e3f427..4e90965 100644 --- a/app/src/main/java/com/cornellappdev/score/model/Game.kt +++ b/app/src/main/java/com/cornellappdev/score/model/Game.kt @@ -2,9 +2,14 @@ package com.cornellappdev.score.model import androidx.compose.ui.graphics.Color import com.cornellappdev.score.R +import com.cornellappdev.score.util.formatDateTimeDisplay import com.cornellappdev.score.util.outputFormatter +import com.cornellappdev.score.util.parseColor import com.cornellappdev.score.util.parseDateOrNull +import com.cornellappdev.score.util.parseDateTimeOrNull +import com.cornellappdev.score.util.toGameData import java.time.LocalDate +import java.time.LocalDateTime // TODO Refactor to make easier to filter... actual gender, etc. @@ -20,7 +25,7 @@ data class Game( data class GameDetailsTeam( val id: String?, - val color: String, + val color: Color, val image: String?, val name: String ) @@ -72,24 +77,29 @@ data class GameCardData( // Data for GameDetailsScreen data class DetailsCardData( + val title: String, val opponentLogo: String, val opponent: String, - val opponentColor: Int, + val opponentColor: Color, val date: LocalDate?, + val time: String, val dateString: String, - val isLive: Boolean, + val isPastStartTime: Boolean, val location: String, + val locationString: String, val gender: String, val genderIcon: Int, val sport: String, val sportIcon: Int, - val boxScore: List, - val scoreBreakdown: List> + val boxScore: List, + val scoreBreakdown: List?>?, + val gameData: GameData, + val scoreEvent:List ) // Scoring information for a specific team, used in the box score data class TeamScore( - val team: Team, + val team: TeamBoxScore, val scoresByPeriod: List, val totalScore: Int ) @@ -114,7 +124,7 @@ data class ScoreEvent( val id: Int, val time: String, val quarter: String, - val team: Team, + val team: TeamGameSummary, val eventType: String, val score: String, val description: String? = null @@ -124,11 +134,13 @@ data class ScoreEvent( val awayScore get() = scoreTuple[1] } -data class Team( +data class TeamBoxScore( + val name: String +) +data class TeamGameSummary( val name: String, - val logo: Int + val logo: String ) - data class GameSummary( val gameData: GameData, val scoreEvents: List @@ -173,4 +185,48 @@ fun Game.toGameCardData(): GameCardData{ sportIcon = Sport.fromDisplayName(sport)?.emptyIcon ?: R.drawable.ic_empty_placeholder ) +} + +fun GameDetailsGame.toGameCardData(): DetailsCardData{ + return DetailsCardData( + title = "Cornell Vs. ${team?.name ?: ""}", + opponentLogo = team?.image ?: "", + opponent = team?.name ?: "", + opponentColor = team?.color ?: Color.White, + date = parseDateOrNull(date), + time = time ?: "", + dateString = formatDateTimeDisplay(date, time ?: ""), + isPastStartTime = parseDateTimeOrNull(date, time ?: "")?.let { + !LocalDateTime.now().isBefore(it) + } ?: false, + location = city, + locationString = "${city}, ${state}", + gender = gender, + genderIcon = if (gender == "Mens") R.drawable.ic_gender_men else R.drawable.ic_gender_women, + sport = sport, + sportIcon = Sport.fromDisplayName(sport)?.emptyIcon + ?: R.drawable.ic_empty_placeholder, + boxScore = boxScore ?: emptyList(), + scoreBreakdown = scoreBreakdown ?: emptyList(), + gameData = toGameData(scoreBreakdown = scoreBreakdown, team1 = TeamBoxScore("Cornell", ), team2 = TeamBoxScore(team?.name ?: "")), + scoreEvent = boxScore?.toScoreEvents(team?.image ?: "") ?: emptyList() + ) +} + +fun List.toScoreEvents(teamLogo: String): List { + return this.mapIndexed { index, boxScore -> + val teamName = boxScore.team ?: "" + val corScore = boxScore.corScore ?: 0 + val oppScore = boxScore.oppScore ?: 0 + + ScoreEvent( + id = index, + time = boxScore.time ?: "", + quarter = boxScore.period ?: "", + team = TeamGameSummary(teamName, logo = teamLogo), + eventType = "Score", // TODO: Change to what ios has and not figma + score = "$corScore - $oppScore", + description = boxScore.description + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/score/model/GameByIdQueryMappers.kt b/app/src/main/java/com/cornellappdev/score/model/GameByIdQueryMappers.kt index fe4213e..81ad01a 100644 --- a/app/src/main/java/com/cornellappdev/score/model/GameByIdQueryMappers.kt +++ b/app/src/main/java/com/cornellappdev/score/model/GameByIdQueryMappers.kt @@ -1,5 +1,6 @@ package com.cornellappdev.score.model +import com.cornellappdev.score.util.parseColor import com.example.score.GameByIdQuery fun GameByIdQuery.Game.toGameDetails(): GameDetailsGame { @@ -22,7 +23,7 @@ fun GameByIdQuery.Game.toGameDetails(): GameDetailsGame { fun GameByIdQuery.Team.toGameDetailsTeam(): GameDetailsTeam { return GameDetailsTeam( id = this.id, - color = this.color, + color = parseColor(this.color).copy(alpha = 0.4f*255), image = this.image, name = this.name ) diff --git a/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt b/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt index 207e7af..c0b8a76 100644 --- a/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt +++ b/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt @@ -75,11 +75,15 @@ class ScoreRepository @Inject constructor( fun getGameById(id: String) = appScope.launch { _currentGameFlow.value = ApiResponse.Loading try { - val response = apolloClient.query(GameByIdQuery(id)).execute() - val game = response.data?.game - - if (game != null) { - _currentGameFlow.value = ApiResponse.Success(game.toGameDetails()) + val result = (apolloClient.query(GameByIdQuery(id)).execute()).toResult() + if (result.isSuccess) { + val game = result.getOrNull() + if (game?.game != null) { + _currentGameFlow.value = ApiResponse.Success(game.game.toGameDetails()) + } else { + Log.e("ScoreRepository", "Game or game.game is null for id: $id") + _currentGameFlow.value = ApiResponse.Error + } } else { _currentGameFlow.value = ApiResponse.Error } 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 11921cf..f686b48 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/GameDetailsScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/GameDetailsScreen.kt @@ -1,5 +1,6 @@ package com.cornellappdev.score.screen +import ScoringSummary import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -7,6 +8,7 @@ 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 @@ -20,20 +22,28 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.res.painterResource +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.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.GameScoreHeader import com.cornellappdev.score.components.TimeUntilStartCard +import com.cornellappdev.score.model.ApiResponse +import com.cornellappdev.score.model.DetailsCardData +import com.cornellappdev.score.model.GameDetailsGame import com.cornellappdev.score.theme.GrayMedium import com.cornellappdev.score.theme.GrayPrimary import com.cornellappdev.score.theme.Style.bodyNormal import com.cornellappdev.score.theme.Style.heading1 import com.cornellappdev.score.theme.Style.heading3 import com.cornellappdev.score.theme.White +import com.cornellappdev.score.viewmodel.GameDetailsUiState import com.cornellappdev.score.viewmodel.GameDetailsViewModel +import com.cornellappdev.score.viewmodel.HomeUiState import com.cornellappdev.score.viewmodel.HomeViewModel @Composable @@ -42,28 +52,67 @@ fun GameDetailsScreen( gameDetailsViewModel: GameDetailsViewModel = hiltViewModel()) { val uiState by gameDetailsViewModel.uiStateFlow.collectAsState() - Column( - modifier = Modifier - .background(White) - .fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Box(modifier = Modifier.height(95.dp)) - val gameCard = uiState.gameCard - if (gameCard == null) { + when (val state = uiState.loadedState) { + is ApiResponse.Loading, ApiResponse.Loading -> { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { CircularProgressIndicator(color = GrayPrimary) } - return + } + + is ApiResponse.Error -> { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Text(text = "Failed to load game.") + } + } + + is ApiResponse.Success -> { + GameDetailsContent(gameCard = state.data) + } + } +} + +@Composable +private fun GameDetailsContent(gameCard: DetailsCardData,){ + Column( + modifier = Modifier + .background(White) + .fillMaxSize(), + ) { + Box(modifier = Modifier.height(27.dp)) + Row( + modifier = Modifier + .fillMaxWidth() + .height(56.dp) + .padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "<", // TODO correct back button + fontSize = 18.sp, + modifier = Modifier + .weight(1f) + ) + Text( + text = "Game Details", + fontSize = 18.sp, + textAlign = TextAlign.Center, + modifier = Modifier.weight(2f) + ) + Spacer(modifier = Modifier.weight(1f)) } GameScoreHeader( leftTeamLogo = painterResource(R.drawable.cornell_logo), - rightTeamLogo = gameCard.team!!.image!!, + rightTeamLogo = gameCard.opponentLogo, gradientColor1 = Color(0xFFE1A69F), - gradientColor2 = Color(gameCard.team.color), + gradientColor2 = gameCard.opponentColor, + leftScore = 0, //TODO Score + rightScore = 0, //TODO Score modifier = Modifier.height(185.dp) ) @@ -75,7 +124,7 @@ fun GameDetailsScreen( style = heading3.copy(color = GrayPrimary) ) Text( - text = "Cornell vs. Yale", + text = gameCard.title, style = heading1.copy(color = GrayPrimary) ) Spacer(modifier = Modifier.height(12.dp)) @@ -90,122 +139,44 @@ fun GameDetailsScreen( colorFilter = ColorFilter.tint(GrayMedium) ) Spacer(modifier = Modifier.width(4.dp)) - Text(text = "Ithaca (Schoellkopf)", style = bodyNormal.copy(color = GrayPrimary)) + Text(text = gameCard.locationString, style = bodyNormal.copy(color = GrayPrimary)) Spacer(modifier = Modifier.width(12.dp)) Image( - painter = painterResource(id = R.drawable.ic_location), - contentDescription = "Location Icon", + painter = painterResource(id = R.drawable.ic_time), + contentDescription = "Time Icon", modifier = Modifier .width(24.dp) .height(24.dp), colorFilter = ColorFilter.tint(GrayMedium) ) Spacer(modifier = Modifier.width(4.dp)) - Text(text = "9/28/2024, 2:00PM", style = bodyNormal.copy(color = GrayPrimary)) + Text(text = gameCard.dateString, style = bodyNormal.copy(color = GrayPrimary)) } //render the below if the game is in the future - Spacer(modifier = Modifier.height(40.dp)) - TimeUntilStartCard(2, 0) - + if(gameCard.isPastStartTime){ + if(!gameCard.scoreBreakdown.isNullOrEmpty()){ + BoxScore(gameCard.gameData) + } + if(gameCard.boxScore.isNotEmpty()){ + ScoringSummary(gameCard.scoreEvent) + } + else{ + Text("No Scoring Summary") // TODO: Make state when there are no scores + } + } + else{ + Spacer(modifier = Modifier.height(40.dp)) + TimeUntilStartCard(2, 0) //TODO Timer + } + } + if(!gameCard.isPastStartTime){ + Spacer(modifier = Modifier.height(84.dp)) + ButtonPrimary("Add to Calendar", painterResource(R.drawable.ic_calendar)) //TODO Calendar } - Spacer(modifier = Modifier.height(84.dp)) - ButtonPrimary("Add to Calendar", painterResource(R.drawable.ic_calendar)) + } } - @Preview @Composable -private fun GameDetailsScreenPreview() { -// GameDetailsScreen() -// import androidx.compose.ui.tooling.preview.Preview -// import androidx.compose.ui.unit.dp -// import com.cornellappdev.score.R -// import com.cornellappdev.score.model.ScoreEvent -// import com.cornellappdev.score.model.Team -// //TODO: Game Header, meta info -// @Composable -// fun GameDetailsScreen(scoreEvents: List, onArrowClick: () -> Unit) { -// Column( -// modifier = Modifier -// .fillMaxWidth() -// .padding(16.dp) -// ) { -// Row( -// modifier = Modifier -// .fillMaxWidth() -// .padding(bottom = 8.dp), -// horizontalArrangement = Arrangement.SpaceBetween, -// verticalAlignment = Alignment.CenterVertically -// ) { -// Text( -// text = "Scoring Summary", -// style = MaterialTheme.typography.titleMedium, -// modifier = Modifier.weight(1f) -// ) - -// Icon( -// imageVector = Icons.Default.LocationOn, -// contentDescription = "Location Icon", -// modifier = Modifier -// .clickable { onArrowClick() } -// .padding(8.dp) -// ) -// } - -// LazyColumn( -// modifier = Modifier.fillMaxWidth() -// ) { -// items(scoreEvents.size) { event -> -// ScoreEventItem(event = scoreEvents[event]) -// Divider(color = Color.LightGray, thickness = 0.5.dp) -// } -// } -// } -// } - - -// @Preview(showBackground = true) -// @Composable -// fun PreviewGameDetailsScreen() { -// // Sample Team and ScoreEvent data -// val team1 = Team(name = "Cornell", logo = R.drawable.cornell_logo) -// val team2 = Team(name = "Yale", logo = R.drawable.yale_logo) - -// val scoreEvents = listOf( -// ScoreEvent( -// id = 1, -// time = "6:21", -// quarter = "1st Quarter", -// team = team1, -// eventType = "Field Goal", -// score = "10 - 7", -// description = "Zhao, Alan field goal attempt from 24 GOOD" -// ), -// ScoreEvent( -// id = 2, -// time = "8:40", -// quarter = "1st Quarter", -// team = team2, -// eventType = "Touchdown", -// score = "7 - 7", -// description = "McCaughey, Brogan right pass complete to Yates, Ry for 8 yards to the COROO, TOUCHDOWN. (Conforti, Nick kick attempt good.)" -// ), -// ScoreEvent( -// id = 3, -// time = "11:29", -// quarter = "1st Quarter", -// team = team1, -// eventType = "Touchdown", -// score = "7 - 0", -// description = "Wang, Jameson left pass complete to Lee, Brendan for 34 yards to the YALOO, TOUCHDOWN. (Zhao, Alan kick attempt good.)" -// ) -// ) - -// GameDetailsScreen( -// scoreEvents = scoreEvents, -// onArrowClick = { -// println("Arrow clicked to view more details") -// } -// ) -} \ No newline at end of file +private fun GameDetailsScreenPreview() {} \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/score/util/DateUtil.kt b/app/src/main/java/com/cornellappdev/score/util/DateUtil.kt index 0fe3def..59bab3a 100644 --- a/app/src/main/java/com/cornellappdev/score/util/DateUtil.kt +++ b/app/src/main/java/com/cornellappdev/score/util/DateUtil.kt @@ -1,7 +1,10 @@ package com.cornellappdev.score.util import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime import java.time.format.DateTimeFormatter; +import java.util.Locale /** * Converts date of form String "month-abbr day (day-of-week)" (for example, "Apr 29 (Tue)") to a LocalDate object @@ -23,3 +26,30 @@ fun parseDateOrNull(strDate: String): LocalDate? { */ val outputFormatter = DateTimeFormatter.ofPattern("M/d/yyyy") +fun parseDateTimeOrNull(strDate: String, strTime: String): LocalDateTime? { + val subDate = strDate.substringBefore(" (") + val cleanedTime = strTime + .replace(".", "") + .trim() + .uppercase() + + val dateTimeString = "$subDate ${LocalDateTime.now().year} $cleanedTime" + + val formatter = DateTimeFormatter.ofPattern("MMM d yyyy h:mm a", Locale.ENGLISH) + + return try { + LocalDateTime.parse(dateTimeString, formatter) + } catch (e: Exception) { + null + } +} + + +fun formatDateTimeDisplay(strDate: String, strTime: String): String { + val dateTime = parseDateTimeOrNull(strDate, strTime) + val formatter = DateTimeFormatter.ofPattern("MMM d, h:mma", Locale.ENGLISH) + + return dateTime?.format(formatter) ?: "" +} + + diff --git a/app/src/main/java/com/cornellappdev/score/util/GameDataUtil.kt b/app/src/main/java/com/cornellappdev/score/util/GameDataUtil.kt new file mode 100644 index 0000000..d2ef974 --- /dev/null +++ b/app/src/main/java/com/cornellappdev/score/util/GameDataUtil.kt @@ -0,0 +1,47 @@ +package com.cornellappdev.score.util + +import com.cornellappdev.score.model.GameData +import com.cornellappdev.score.model.TeamBoxScore +import com.cornellappdev.score.model.TeamScore + +fun toGameData( + scoreBreakdown: List?>?, + team1: TeamBoxScore, + team2: TeamBoxScore +): GameData { + fun convertScores(scoreList: List?): Pair, Int> { + if (scoreList == null || scoreList.size < 2) return Pair(emptyList(), 0) + + val scoresByPeriod = scoreList + .subList(0, scoreList.size - 1) + .map { + when { + it == null -> 0 + it.uppercase() == "X" -> 0 + else -> it.toIntOrNull() ?: 0 + } + } + + val totalScore = when (val totalLast = scoreList.last()) { + null -> 0 + else -> totalLast.toIntOrNull() ?: 0 + } + + return Pair(scoresByPeriod, totalScore) + } + + val (team1Scores, team1Total) = if (!scoreBreakdown.isNullOrEmpty() && scoreBreakdown.isNotEmpty()) + convertScores(scoreBreakdown[0]) + else Pair(emptyList(), 0) + + val (team2Scores, team2Total) = if (scoreBreakdown != null && scoreBreakdown.size > 1) + convertScores(scoreBreakdown[1]) + else Pair(emptyList(), 0) + + val team1Score = TeamScore(team = team1, scoresByPeriod = team1Scores, totalScore = team1Total) + val team2Score = TeamScore(team = team2, scoresByPeriod = team2Scores, totalScore = team2Total) + + return GameData(teamScores = Pair(team1Score, team2Score)) +} + + diff --git a/app/src/main/java/com/cornellappdev/score/util/TestingConstants.kt b/app/src/main/java/com/cornellappdev/score/util/TestingConstants.kt index ac4c45a..783f8a8 100644 --- a/app/src/main/java/com/cornellappdev/score/util/TestingConstants.kt +++ b/app/src/main/java/com/cornellappdev/score/util/TestingConstants.kt @@ -7,7 +7,8 @@ import com.cornellappdev.score.model.GameData import com.cornellappdev.score.model.ScoreEvent import com.cornellappdev.score.model.Sport import com.cornellappdev.score.model.SportSelection -import com.cornellappdev.score.model.Team +import com.cornellappdev.score.model.TeamBoxScore +import com.cornellappdev.score.model.TeamGameSummary import com.cornellappdev.score.model.TeamScore import java.time.LocalDate @@ -40,8 +41,8 @@ val PRINCETON_GAME = GameCardData( ) val gameList = listOf(PENN_GAME, PRINCETON_GAME) -val team1 = Team(name = "Cornell", R.drawable.cornell_logo) -val team2 = Team(name = "Yale", R.drawable.yale_logo) +val team1 = TeamBoxScore(name = "Cornell") +val team2 = TeamBoxScore(name = "Yale") val teamScore1 = TeamScore( team = team1, @@ -56,12 +57,14 @@ val teamScore2 = TeamScore( val gameData = GameData(teamScores = Pair(teamScore1, teamScore2)) +val team3 = TeamGameSummary(name = "Cornell", "https://cornellbigred.com/images/logos/penn_200x200.png?width=80&height=80&mode=max") +val team4 = TeamGameSummary(name = "Yale", "https://cornellbigred.com/images/logos/penn_200x200.png?width=80&height=80&mode=max") val scoreEvents1 = listOf( ScoreEvent( id = 1, time = "6:21", quarter = "1st Quarter", - team = team1, + team = team3, eventType = "Field Goal", score = "10 - 7" ), @@ -69,7 +72,7 @@ val scoreEvents1 = listOf( id = 2, time = "8:40", quarter = "1st Quarter", - team = team2, + team = team4, eventType = "Touchdown", score = "7 - 7" ), @@ -77,7 +80,7 @@ val scoreEvents1 = listOf( id = 3, time = "11:29", quarter = "1st Quarter", - team = team1, + team = team3, eventType = "Touchdown", score = "7 - 0" ) @@ -87,7 +90,7 @@ val scoreEvents2 = listOf( id = 1, time = "6:21", quarter = "1st Quarter", - team = team1, + team = team3, eventType = "Field Goal", score = "10 - 7", description = "Zhao, Alan field goal attempt from 24 GOOD" @@ -96,7 +99,7 @@ val scoreEvents2 = listOf( id = 2, time = "8:40", quarter = "1st Quarter", - team = team2, + team = team4, eventType = "Touchdown", score = "7 - 7", description = "McCaughey, Brogan right pass complete to Yates, Ry for 8 yards to the COROO, TOUCHDOWN. (Conforti, Nick kick attempt good.)" @@ -105,7 +108,7 @@ val scoreEvents2 = listOf( id = 3, time = "11:29", quarter = "1st Quarter", - team = team1, + team = team3, eventType = "Touchdown", score = "7 - 0", description = "Wang, Jameson left pass complete to Lee, Brendan for 34 yards to the YALOO, TOUCHDOWN. (Zhao, Alan kick attempt good.)" diff --git a/app/src/main/java/com/cornellappdev/score/viewmodel/GameDetailsViewModel.kt b/app/src/main/java/com/cornellappdev/score/viewmodel/GameDetailsViewModel.kt index 4e64304..678be6d 100644 --- a/app/src/main/java/com/cornellappdev/score/viewmodel/GameDetailsViewModel.kt +++ b/app/src/main/java/com/cornellappdev/score/viewmodel/GameDetailsViewModel.kt @@ -2,120 +2,38 @@ package com.cornellappdev.score.viewmodel import android.util.Log import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.viewModelScope -import com.cornellappdev.score.R import com.cornellappdev.score.model.ApiResponse -import com.cornellappdev.score.model.Game -import com.cornellappdev.score.model.GameCardData -import com.cornellappdev.score.model.GameDetailsGame +import com.cornellappdev.score.model.DetailsCardData import com.cornellappdev.score.model.ScoreRepository -import com.cornellappdev.score.model.Sport -import com.cornellappdev.score.nav.root.RootNavigationRepository +import com.cornellappdev.score.model.map +import com.cornellappdev.score.model.toGameCardData import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch -import java.time.LocalDate import javax.inject.Inject +data class GameDetailsUiState( + val loadedState: ApiResponse +) + @HiltViewModel class GameDetailsViewModel @Inject constructor( - private val rootNavigationRepository: RootNavigationRepository, - private val scoreRepository: ScoreRepository, - private val savedStateHandle: SavedStateHandle - ) : BaseViewModel( + scoreRepository: ScoreRepository, + savedStateHandle: SavedStateHandle +) : BaseViewModel( initialUiState = GameDetailsUiState( - gameCard = null, - isLive = false + loadedState = ApiResponse.Loading ) ) { init { - observeCurrentGame() - viewModelScope.launch{ - val gameId: String? = savedStateHandle["gameId"] - scoreRepository.getGameById(gameId!!) - } - } - private fun observeCurrentGame() = scoreRepository.currentGamesFlow.onEach { response -> - Log.d("HomeViewModel", "Response: $response") - updateGameCard(response) - }.launchIn(viewModelScope) - - private fun updateGameCard(response: ApiResponse) { - val game: GameDetailsGame? = when (response) { - is ApiResponse.Success -> { - Log.d("updateGameList", "Success") - response.data - } - ApiResponse.Error -> { - Log.d("updateGameList", "Error") - null - } - ApiResponse.Loading -> { - Log.d("updateGameList", "Loading") - null + val gameId: String? = savedStateHandle["gameId"] + scoreRepository.getGameById(gameId ?: "") + asyncCollect(scoreRepository.currentGamesFlow) { response -> + applyMutation { + copy( + loadedState = response.map { gameCard -> + gameCard.toGameCardData() + } + ) } } - if (game == null){ - return - } - val currentDate = LocalDate.now() - val tomorrowDate = LocalDate.now().plusDays(1) - val formattedDate = formatDate(game.date) - - applyMutation { - copy(gameCard = gameCard) - } } - private fun formatDate(strDate: String): LocalDate? { - val monthMap = mapOf( - "Jan" to 1, - "Feb" to 2, - "Mar" to 3, - "Apr" to 4, - "May" to 5, - "Jun" to 6, - "Jul" to 7, - "Aug" to 8, - "Sep" to 9, - "Oct" to 10, - "Nov" to 11, - "Dec" to 12 - ) - - val parts = strDate.split(" ") - if (parts.size < 2) return null - - val month = monthMap[parts[0]] - if (month == null) { - return null - } - val day = parts[1].toIntOrNull() ?: return null - - val currentYear = LocalDate.now().year - //Log.d("HomeViewModel", "formatDate: ${LocalDate.of(currentYear, month, day)}") - return LocalDate.of(currentYear, month, day) - } - - /** - * Converts from format "#xxxxxx" to a valid hex, with alpha = 40. Ready to be passed into Color() - */ - - private fun formatColor(color: String): Int { - val alpha = (40 * 255 / 100)// Convert percent to hex (0-255) - val colorInt = Integer.parseInt(color.removePrefix("#"), 16) - return (alpha shl 24) or colorInt - } - - private fun dateToString(date: LocalDate?): String { - if (date == null) { - return "--" - } - //Log.d("HomeViewModel", "formatedDate: ${date.month.value}/${date.dayOfMonth}/${date.year}") - return "${date.month.value}/${date.dayOfMonth}/${date.year}" - } - data class GameDetailsUiState( - val gameCard: GameDetailsGame?, - val isLive: Boolean - ) } \ No newline at end of file From e86d68b8c036773db376aaf069af6be1523d2a0c Mon Sep 17 00:00:00 2001 From: Emil Jiang Date: Mon, 24 Mar 2025 10:06:01 -0400 Subject: [PATCH 06/19] lint fix --- .../cornellappdev/score/screen/GameScoreSummaryScreen.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 5d9fcc6..a38853f 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/GameScoreSummaryScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/GameScoreSummaryScreen.kt @@ -28,6 +28,7 @@ import com.cornellappdev.score.theme.Style.bodyNormal import com.cornellappdev.score.theme.Style.spanBodyNormal import com.cornellappdev.score.util.scoreEvents2 import androidx.compose.foundation.layout.fillMaxSize +import coil3.compose.AsyncImage @Composable fun GameScoreSummaryScreenDetail(scoreEvents: List) { @@ -54,11 +55,10 @@ fun ScoreEventItemDetailed(event: ScoreEvent) { verticalAlignment = Alignment.Top, horizontalArrangement = Arrangement.SpaceBetween ) { - Image( - painter = painterResource(event.team.logo), + AsyncImage( + model = event.team.logo, contentDescription = event.team.name, - modifier = Modifier - .size(40.dp) + modifier = Modifier.size(40.dp) ) Spacer(modifier = Modifier.width(8.dp)) Column( From f621fd2f740259d6a148be7689ef7bb1c8a0b6e0 Mon Sep 17 00:00:00 2001 From: Emil Jiang Date: Mon, 24 Mar 2025 14:57:05 -0400 Subject: [PATCH 07/19] nav --- .../cornellappdev/score/nav/root/RootNavigation.kt | 13 +++++++++---- .../com/cornellappdev/score/screen/HomeScreen.kt | 5 ++++- 2 files changed, 13 insertions(+), 5 deletions(-) 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 440ad57..e84978a 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 @@ -6,6 +6,8 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController +import androidx.navigation.toRoute +import com.cornellappdev.score.screen.GameDetailsScreen import com.cornellappdev.score.screen.HomeScreen import kotlinx.serialization.Serializable @@ -27,11 +29,14 @@ fun RootNavigation( startDestination = ScoreRootScreens.Home ) { composable { - HomeScreen() + HomeScreen(navigate = { id -> + navController.navigate(ScoreRootScreens.GameDetailPage(id)) + }) } - composable { - + composable {navBackStackEntry -> + val id = navBackStackEntry.toRoute().gameId + GameDetailsScreen(id) } composable { @@ -47,7 +52,7 @@ sealed class ScoreRootScreens { data object Home : ScoreRootScreens() @Serializable - data object GameDetailPage : ScoreRootScreens() + data class GameDetailPage(val gameId: String) : ScoreRootScreens() @Serializable data object Onboarding : ScoreRootScreens() 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 c8c6478..ec67ebf 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/HomeScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/HomeScreen.kt @@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -36,7 +37,8 @@ import com.cornellappdev.score.viewmodel.HomeViewModel @Composable fun HomeScreen( - homeViewModel: HomeViewModel = hiltViewModel() + homeViewModel: HomeViewModel = hiltViewModel(), + navigate: (String) -> Unit ) { val uiState = homeViewModel.collectUiStateValue() @@ -44,6 +46,7 @@ fun HomeScreen( verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top), modifier = Modifier.statusBarsPadding() ) { + Button(onClick = {navigate("67a51d698bc06c5edeba401d")}) { } when (uiState.loadedState) { is ApiResponse.Loading -> { //TODO: Add loading screen From 0a44180be820ce51a05ecf1f64a7a88798839bac Mon Sep 17 00:00:00 2001 From: Emil Jiang Date: Sun, 6 Apr 2025 00:05:07 -0400 Subject: [PATCH 08/19] pr changes --- .../com/cornellappdev/score/model/Game.kt | 32 ++++- .../score/nav/root/RootNavigation.kt | 3 +- .../score/screen/GameDetailsScreen.kt | 114 ++++++++---------- .../cornellappdev/score/screen/HomeScreen.kt | 2 + .../cornellappdev/score/util/CalendarUtil.kt | 34 ++++++ .../com/cornellappdev/score/util/DateUtil.kt | 47 ++++++-- .../cornellappdev/score/util/GameDataUtil.kt | 77 +++++++----- .../score/viewmodel/GameDetailsViewModel.kt | 25 ++-- 8 files changed, 215 insertions(+), 119 deletions(-) create mode 100644 app/src/main/java/com/cornellappdev/score/util/CalendarUtil.kt 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 fe9d523..b7f5cbf 100644 --- a/app/src/main/java/com/cornellappdev/score/model/Game.kt +++ b/app/src/main/java/com/cornellappdev/score/model/Game.kt @@ -2,11 +2,13 @@ package com.cornellappdev.score.model import androidx.compose.ui.graphics.Color import com.cornellappdev.score.R +import com.cornellappdev.score.util.convertScores import com.cornellappdev.score.util.formatDateTimeDisplay +import com.cornellappdev.score.util.getTimeUntilStart import com.cornellappdev.score.util.outputFormatter -import com.cornellappdev.score.util.parseColor 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 java.time.LocalDate import java.time.LocalDateTime @@ -67,7 +69,6 @@ data class GameDetailsGame( ) - // Data for HomeScreen game displays data class GameCardData( val teamLogo: String, @@ -113,7 +114,11 @@ data class DetailsCardData( val boxScore: List, val scoreBreakdown: List?>?, val gameData: GameData, - val scoreEvent:List + val scoreEvent: List, + val daysUntilGame: Int?, + val hoursUntilGame: Int?, + val homeScore: Int, + val oppScore: Int ) // Scoring information for a specific team, used in the box score @@ -156,10 +161,12 @@ data class ScoreEvent( data class TeamBoxScore( val name: String ) + data class TeamGameSummary( val name: String, val logo: String ) + data class GameSummary( val gameData: GameData, val scoreEvents: List @@ -212,7 +219,9 @@ fun Game.toGameCardData(): GameCardData { ) } -fun GameDetailsGame.toGameCardData(): DetailsCardData{ +fun GameDetailsGame.toGameCardData(): DetailsCardData { + val (daysUntil, hoursUntil) = getTimeUntilStart(date, time ?: "") ?: (null to null) + val parsedScores = parseResultScore(result) return DetailsCardData( title = "Cornell Vs. ${team?.name ?: ""}", opponentLogo = team?.image ?: "", @@ -233,8 +242,19 @@ fun GameDetailsGame.toGameCardData(): DetailsCardData{ ?: R.drawable.ic_empty_placeholder, boxScore = boxScore ?: emptyList(), scoreBreakdown = scoreBreakdown ?: emptyList(), - gameData = toGameData(scoreBreakdown = scoreBreakdown, team1 = TeamBoxScore("Cornell", ), team2 = TeamBoxScore(team?.name ?: "")), - scoreEvent = boxScore?.toScoreEvents(team?.image ?: "") ?: emptyList() + gameData = toGameData( + scoreBreakdown = scoreBreakdown, + team1 = TeamBoxScore("Cornell"), + team2 = TeamBoxScore(team?.name ?: ""), + sport = sport + ), + scoreEvent = boxScore?.toScoreEvents(team?.image ?: "") ?: emptyList(), + daysUntilGame = daysUntil, + hoursUntilGame = hoursUntil, + homeScore = convertScores(scoreBreakdown?.getOrNull(0), sport).second + ?: parsedScores?.first ?: 0, + oppScore = convertScores(scoreBreakdown?.getOrNull(1), sport).second + ?: parsedScores?.second ?: 0 ) } 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 46181d0..84eca08 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 @@ -30,7 +30,6 @@ import com.cornellappdev.score.theme.GrayPrimary import com.cornellappdev.score.theme.Style.bodyMedium import com.cornellappdev.score.theme.White import kotlinx.serialization.Serializable -import java.time.LocalDate @Composable fun RootNavigation( @@ -84,7 +83,7 @@ fun RootNavigation( ) { composable { HomeScreen(navigateToGameDetails = { - navController.navigate(ScoreRootScreens.GameDetailsPage("")) + navController.navigate(ScoreRootScreens.GameDetailsPage("67e585b941adb1bafec37766")) }) } 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 54eb07d..cea878e 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/GameDetailsScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/GameDetailsScreen.kt @@ -8,7 +8,6 @@ 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 @@ -21,11 +20,9 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource -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.hilt.navigation.compose.hiltViewModel import com.cornellappdev.score.R import com.cornellappdev.score.components.BoxScore @@ -35,48 +32,54 @@ import com.cornellappdev.score.components.NavigationHeader import com.cornellappdev.score.components.TimeUntilStartCard import com.cornellappdev.score.model.ApiResponse import com.cornellappdev.score.model.DetailsCardData -import com.cornellappdev.score.model.GameDetailsGame import com.cornellappdev.score.theme.GrayMedium import com.cornellappdev.score.theme.GrayPrimary import com.cornellappdev.score.theme.Style.bodyNormal import com.cornellappdev.score.theme.Style.heading1 import com.cornellappdev.score.theme.Style.heading3 import com.cornellappdev.score.theme.White -import com.cornellappdev.score.viewmodel.GameDetailsUiState +import com.cornellappdev.score.util.addToCalendar import com.cornellappdev.score.viewmodel.GameDetailsViewModel -import com.cornellappdev.score.viewmodel.HomeUiState -import com.cornellappdev.score.viewmodel.HomeViewModel @Composable fun GameDetailsScreen( gameId: String, gameDetailsViewModel: GameDetailsViewModel = hiltViewModel(), - onBackArrow: () -> Unit = {}) -{ + onBackArrow: () -> Unit = {} +) { val uiState by gameDetailsViewModel.uiStateFlow.collectAsState() - when (val state = uiState.loadedState) { - is ApiResponse.Loading, ApiResponse.Loading -> { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - CircularProgressIndicator(color = GrayPrimary) + Column(modifier = Modifier + .fillMaxSize() + .background(White) + ) { + NavigationHeader( + title = "Game Details", + onBackPressed = onBackArrow + ) + when (val state = uiState.loadedState) { + is ApiResponse.Loading, ApiResponse.Loading -> { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator(color = GrayPrimary) + } } - } - is ApiResponse.Error -> { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - Text(text = "Failed to load game.") + is ApiResponse.Error -> { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Text(text = "Failed to load game.") + } } - } - is ApiResponse.Success -> { - GameDetailsContent( - gameCard = state.data, onBackArrow = onBackArrow + is ApiResponse.Success -> { + GameDetailsContent( + gameCard = state.data ) + } } } } @@ -84,35 +87,13 @@ fun GameDetailsScreen( @Composable private fun GameDetailsContent( gameCard: DetailsCardData, - onBackArrow: () -> Unit = {}){ +) { Column( modifier = Modifier .background(White) .fillMaxSize(), ) { - NavigationHeader(title = "Game Details", onBackPressed = onBackArrow) Box(modifier = Modifier.height(27.dp)) - Row( - modifier = Modifier - .fillMaxWidth() - .height(56.dp) - .padding(horizontal = 16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = "<", // TODO correct back button - fontSize = 18.sp, - modifier = Modifier - .weight(1f) - ) - Text( - text = "Game Details", - fontSize = 18.sp, - textAlign = TextAlign.Center, - modifier = Modifier.weight(2f) - ) - Spacer(modifier = Modifier.weight(1f)) - } GameScoreHeader( leftTeamLogo = painterResource(R.drawable.cornell_logo), rightTeamLogo = gameCard.opponentLogo, @@ -161,29 +142,34 @@ private fun GameDetailsContent( } //render the below if the game is in the future - if(gameCard.isPastStartTime){ - if(!gameCard.scoreBreakdown.isNullOrEmpty()){ + if (gameCard.isPastStartTime) { + if (!gameCard.scoreBreakdown.isNullOrEmpty()) { BoxScore(gameCard.gameData) } - if(gameCard.boxScore.isNotEmpty()){ + if (gameCard.boxScore.isNotEmpty()) { ScoringSummary(gameCard.scoreEvent) - } - else{ + } else { Text("No Scoring Summary") // TODO: Make state when there are no scores } - } - else{ + } else { Spacer(modifier = Modifier.height(40.dp)) - TimeUntilStartCard(2, 0) //TODO Timer + if (gameCard.daysUntilGame != null && gameCard.hoursUntilGame != null) { + TimeUntilStartCard( + gameCard.daysUntilGame, + gameCard.hoursUntilGame + ) + } } } - if(!gameCard.isPastStartTime){ + if (!gameCard.isPastStartTime) { + val context = LocalContext.current Spacer(modifier = Modifier.height(84.dp)) - ButtonPrimary("Add to Calendar", painterResource(R.drawable.ic_calendar)) //TODO Calendar + ButtonPrimary( + "Add to Calendar", + painterResource(R.drawable.ic_calendar), + onClick = { addToCalendar(context = context, gameCard) } + ) //TODO polish calendar } } } -@Preview -@Composable -private fun GameDetailsScreenPreview() {} \ No newline at end of file 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 ac51f88..11cda21 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/HomeScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/HomeScreen.kt @@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -48,6 +49,7 @@ fun HomeScreen( .statusBarsPadding() .background(Color.White) ) { + Button(onClick = { navigateToGameDetails(true) }) { } when (uiState.loadedState) { is ApiResponse.Loading -> { //TODO: Add loading screen diff --git a/app/src/main/java/com/cornellappdev/score/util/CalendarUtil.kt b/app/src/main/java/com/cornellappdev/score/util/CalendarUtil.kt new file mode 100644 index 0000000..a5a4f48 --- /dev/null +++ b/app/src/main/java/com/cornellappdev/score/util/CalendarUtil.kt @@ -0,0 +1,34 @@ +package com.cornellappdev.score.util + +import android.content.Context +import android.content.Intent +import android.provider.CalendarContract +import com.cornellappdev.score.model.DetailsCardData +import java.time.ZoneId + +fun addToCalendar(context: Context, details: DetailsCardData) { + val startDateTime = details.date?.atTime( + details.time.split(":").getOrNull(0)?.toIntOrNull() ?: 0, + details.time.split(":").getOrNull(1)?.toIntOrNull() ?: 0 + ) ?: return + + val startMillis = startDateTime + .atZone(ZoneId.systemDefault()) + .toInstant() + .toEpochMilli() + + val endMillis = startMillis + (2 * 60 * 60 * 1000) // 2 hours later + + val intent = Intent(Intent.ACTION_INSERT).apply { + data = CalendarContract.Events.CONTENT_URI + putExtra(CalendarContract.Events.TITLE, "Cornell vs. ${details.opponent}") + putExtra(CalendarContract.Events.EVENT_LOCATION, details.locationString) + putExtra(CalendarContract.Events.DESCRIPTION, "${details.sport} game (${details.gender})") + putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, startMillis) + putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endMillis) + } + + if (intent.resolveActivity(context.packageManager) != null) { + context.startActivity(intent) + } +} diff --git a/app/src/main/java/com/cornellappdev/score/util/DateUtil.kt b/app/src/main/java/com/cornellappdev/score/util/DateUtil.kt index 59bab3a..c114a25 100644 --- a/app/src/main/java/com/cornellappdev/score/util/DateUtil.kt +++ b/app/src/main/java/com/cornellappdev/score/util/DateUtil.kt @@ -1,9 +1,10 @@ package com.cornellappdev.score.util +import android.util.Log +import java.time.Duration import java.time.LocalDate import java.time.LocalDateTime -import java.time.LocalTime -import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatter import java.util.Locale /** @@ -26,15 +27,25 @@ fun parseDateOrNull(strDate: String): LocalDate? { */ val outputFormatter = DateTimeFormatter.ofPattern("M/d/yyyy") -fun parseDateTimeOrNull(strDate: String, strTime: String): LocalDateTime? { - val subDate = strDate.substringBefore(" (") - val cleanedTime = strTime +/** + * Parses a date and time string into a [LocalDateTime] object. + * + * Expected input format: + * - [date]: String of form "MMM d (EEE)". For example, "Apr 5 (Sat)" + * - [time]: String of form "h:mm a" with or without periods. For example, "3:45 PM" or "3:45 p.m." + * + * The current year is assumed in the final parsed result. + * returns [LocalDateTime] object if parsing succeeds, or [null] if the format is invalid. + */ +fun parseDateTimeOrNull(date: String, time: String): LocalDateTime? { + val subDate = date.substringBefore(" (") + date.substringAfter(")") + val cleanedTime = time .replace(".", "") .trim() .uppercase() - val dateTimeString = "$subDate ${LocalDateTime.now().year} $cleanedTime" - + val dateTimeString = "$subDate $cleanedTime" + Log.d("parseDateTimeOrNull", "parseDateTimeOrNull: ${dateTimeString}") val formatter = DateTimeFormatter.ofPattern("MMM d yyyy h:mm a", Locale.ENGLISH) return try { @@ -45,11 +56,29 @@ fun parseDateTimeOrNull(strDate: String, strTime: String): LocalDateTime? { } -fun formatDateTimeDisplay(strDate: String, strTime: String): String { - val dateTime = parseDateTimeOrNull(strDate, strTime) +/** + * Formats a date and time string into a user-friendly display string. + * + * Combines [date] and [time] inputs and attempts to parse them using [parseDateTimeOrNull]. + * If parsing succeeds, returns a string in the format "MMM d, h:mma" (For example, "Apr 5, 3:45PM"). + * If parsing fails, returns an empty string. + */ +fun formatDateTimeDisplay(date: String, time: String): String { + val dateTime = parseDateTimeOrNull(date, time) val formatter = DateTimeFormatter.ofPattern("MMM d, h:mma", Locale.ENGLISH) return dateTime?.format(formatter) ?: "" } +fun getTimeUntilStart(date: String, time: String): Pair? { + val gameStart = parseDateTimeOrNull(date, time) ?: return null + val now = LocalDateTime.now() + + if (gameStart.isBefore(now)) return null + + val duration = Duration.between(now, gameStart) + val days = duration.toDays().toInt() + val hours = duration.minusDays(days.toLong()).toHours().toInt() + return Pair(days, hours) +} diff --git a/app/src/main/java/com/cornellappdev/score/util/GameDataUtil.kt b/app/src/main/java/com/cornellappdev/score/util/GameDataUtil.kt index d2ef974..75956f6 100644 --- a/app/src/main/java/com/cornellappdev/score/util/GameDataUtil.kt +++ b/app/src/main/java/com/cornellappdev/score/util/GameDataUtil.kt @@ -4,44 +4,67 @@ import com.cornellappdev.score.model.GameData import com.cornellappdev.score.model.TeamBoxScore import com.cornellappdev.score.model.TeamScore -fun toGameData( - scoreBreakdown: List?>?, - team1: TeamBoxScore, - team2: TeamBoxScore -): GameData { - fun convertScores(scoreList: List?): Pair, Int> { - if (scoreList == null || scoreList.size < 2) return Pair(emptyList(), 0) - - val scoresByPeriod = scoreList - .subList(0, scoreList.size - 1) - .map { - when { - it == null -> 0 - it.uppercase() == "X" -> 0 - else -> it.toIntOrNull() ?: 0 - } - } +fun convertScores(scoreList: List?, sport: String): Pair, Int?> { + if (scoreList == null || scoreList.size < 2) return Pair(emptyList(), null) - val totalScore = when (val totalLast = scoreList.last()) { - null -> 0 - else -> totalLast.toIntOrNull() ?: 0 + var scoresByPeriod = scoreList + .subList(0, scoreList.size - 1) + .map { + when { + it == null -> 0 + it.uppercase() == "X" -> 0 + else -> it.toIntOrNull() ?: 0 + } } + if (sport.lowercase() == "baseball") { + scoresByPeriod = scoresByPeriod.take(9) + val totalScore = scoresByPeriod.sum() return Pair(scoresByPeriod, totalScore) } - val (team1Scores, team1Total) = if (!scoreBreakdown.isNullOrEmpty() && scoreBreakdown.isNotEmpty()) - convertScores(scoreBreakdown[0]) - else Pair(emptyList(), 0) + val totalScore = scoreList.last()?.toIntOrNull() + return Pair(scoresByPeriod, totalScore) +} + +fun toGameData( + scoreBreakdown: List?>?, + team1: TeamBoxScore, + team2: TeamBoxScore, + sport: String +): GameData { + val (team1Scores, team1Total) = if (!scoreBreakdown.isNullOrEmpty()) + convertScores(scoreBreakdown[0], sport) + else Pair(emptyList(), null) val (team2Scores, team2Total) = if (scoreBreakdown != null && scoreBreakdown.size > 1) - convertScores(scoreBreakdown[1]) - else Pair(emptyList(), 0) + convertScores(scoreBreakdown[1], sport) + else Pair(emptyList(), null) - val team1Score = TeamScore(team = team1, scoresByPeriod = team1Scores, totalScore = team1Total) - val team2Score = TeamScore(team = team2, scoresByPeriod = team2Scores, totalScore = team2Total) + val team1Score = + TeamScore(team = team1, scoresByPeriod = team1Scores, totalScore = team1Total ?: 0) + val team2Score = + TeamScore(team = team2, scoresByPeriod = team2Scores, totalScore = team2Total ?: 0) return GameData(teamScores = Pair(team1Score, team2Score)) } +fun parseResultScore(result: String?): Pair? { + if (result.isNullOrBlank()) return null + + val parts = result.split(",") + if (parts.size != 2) return null + + val scorePart = parts[1].split("-") + if (scorePart.size != 2) return null + + val homeScore = scorePart[0].toIntOrNull() + val oppScore = scorePart[1].toIntOrNull() + + if (homeScore != null && oppScore != null) { + return Pair(homeScore, oppScore) + } else { + return null + } +} diff --git a/app/src/main/java/com/cornellappdev/score/viewmodel/GameDetailsViewModel.kt b/app/src/main/java/com/cornellappdev/score/viewmodel/GameDetailsViewModel.kt index 678be6d..031f5d5 100644 --- a/app/src/main/java/com/cornellappdev/score/viewmodel/GameDetailsViewModel.kt +++ b/app/src/main/java/com/cornellappdev/score/viewmodel/GameDetailsViewModel.kt @@ -1,6 +1,5 @@ package com.cornellappdev.score.viewmodel -import android.util.Log import androidx.lifecycle.SavedStateHandle import com.cornellappdev.score.model.ApiResponse import com.cornellappdev.score.model.DetailsCardData @@ -19,21 +18,25 @@ class GameDetailsViewModel @Inject constructor( scoreRepository: ScoreRepository, savedStateHandle: SavedStateHandle ) : BaseViewModel( - initialUiState = GameDetailsUiState( - loadedState = ApiResponse.Loading + initialUiState = GameDetailsUiState( + loadedState = ApiResponse.Loading ) ) { init { val gameId: String? = savedStateHandle["gameId"] - scoreRepository.getGameById(gameId ?: "") - asyncCollect(scoreRepository.currentGamesFlow) { response -> - applyMutation { - copy( - loadedState = response.map { gameCard -> - gameCard.toGameCardData() - } - ) + gameId?.let { + scoreRepository.getGameById(it) + asyncCollect(scoreRepository.currentGamesFlow) { response -> + applyMutation { + copy( + loadedState = response.map { gameCard -> + gameCard.toGameCardData() + } + ) + } } + } ?: applyMutation { + copy(loadedState = ApiResponse.Error) } } } \ No newline at end of file From c25f92739c448ff8e084a7f2bb916ec09a1e1a13 Mon Sep 17 00:00:00 2001 From: Emil Jiang Date: Sun, 6 Apr 2025 00:10:46 -0400 Subject: [PATCH 09/19] new changes --- .../score/model/ScoreRepository.kt | 23 +++++++------------ .../score/nav/root/RootNavigation.kt | 2 +- .../score/screen/GameDetailsScreen.kt | 12 +++++----- 3 files changed, 15 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt b/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt index 4476866..849a1ce 100644 --- a/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt +++ b/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt @@ -2,17 +2,16 @@ package com.cornellappdev.score.model import android.util.Log import com.apollographql.apollo.ApolloClient +import com.cornellappdev.score.util.parseColor import com.example.score.GameByIdQuery import com.example.score.GamesQuery import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject import javax.inject.Singleton -import com.cornellappdev.score.util.parseColor /** * This is a singleton responsible for fetching and caching all data for Score. @@ -29,8 +28,10 @@ class ScoreRepository @Inject constructor( MutableStateFlow>>(ApiResponse.Loading) val upcomingGamesFlow = _upcomingGamesFlow.asStateFlow() - private val _currentGameFlow = MutableStateFlow>(ApiResponse.Loading) + private val _currentGameFlow = + MutableStateFlow>(ApiResponse.Loading) val currentGamesFlow = _currentGameFlow.asStateFlow() + /** * Asynchronously fetches the list of games from the API. Once finished, will send down * `upcomingGamesFlow` to be observed. @@ -90,17 +91,9 @@ class ScoreRepository @Inject constructor( _currentGameFlow.value = ApiResponse.Loading try { val result = (apolloClient.query(GameByIdQuery(id)).execute()).toResult() - if (result.isSuccess) { - val game = result.getOrNull() - if (game?.game != null) { - _currentGameFlow.value = ApiResponse.Success(game.game.toGameDetails()) - } else { - Log.e("ScoreRepository", "Game or game.game is null for id: $id") - _currentGameFlow.value = ApiResponse.Error - } - } else { - _currentGameFlow.value = ApiResponse.Error - } + result.getOrNull()?.game?.let { + _currentGameFlow.value = ApiResponse.Success(it.toGameDetails()) + } ?: _currentGameFlow.update { ApiResponse.Error } } catch (e: Exception) { Log.e("ScoreRepository", "Error fetching game with id: ${id}: ", e) _currentGameFlow.value = ApiResponse.Error 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 84eca08..3429fb4 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 @@ -88,7 +88,7 @@ fun RootNavigation( } composable { - GameDetailsScreen("", onBackArrow = { + GameDetailsScreen(onBackArrow = { navController.navigateUp() }) 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 cea878e..2173b0a 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/GameDetailsScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/GameDetailsScreen.kt @@ -43,14 +43,14 @@ import com.cornellappdev.score.viewmodel.GameDetailsViewModel @Composable fun GameDetailsScreen( - gameId: String, gameDetailsViewModel: GameDetailsViewModel = hiltViewModel(), onBackArrow: () -> Unit = {} ) { val uiState by gameDetailsViewModel.uiStateFlow.collectAsState() - Column(modifier = Modifier - .fillMaxSize() - .background(White) + Column( + modifier = Modifier + .fillMaxSize() + .background(White) ) { NavigationHeader( title = "Game Details", @@ -99,8 +99,8 @@ private fun GameDetailsContent( rightTeamLogo = gameCard.opponentLogo, gradientColor1 = Color(0xFFE1A69F), gradientColor2 = gameCard.opponentColor, - leftScore = 0, //TODO Score - rightScore = 0, //TODO Score + leftScore = gameCard.homeScore, + rightScore = gameCard.oppScore, //TODO Score modifier = Modifier.height(185.dp) ) From 567d2952f257e0e9261c7887bbc203b47cd8565b Mon Sep 17 00:00:00 2001 From: Emil Jiang Date: Wed, 9 Apr 2025 00:59:45 -0400 Subject: [PATCH 10/19] ui changes --- .../score/components/NavigationHeader.kt | 69 ++++++++----------- .../score/screen/GameDetailsScreen.kt | 18 +++-- 2 files changed, 44 insertions(+), 43 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/score/components/NavigationHeader.kt b/app/src/main/java/com/cornellappdev/score/components/NavigationHeader.kt index 96bc2f6..a2f2310 100644 --- a/app/src/main/java/com/cornellappdev/score/components/NavigationHeader.kt +++ b/app/src/main/java/com/cornellappdev/score/components/NavigationHeader.kt @@ -1,68 +1,59 @@ package com.cornellappdev.score.components -import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer 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.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults 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 import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.cornellappdev.score.R -import com.cornellappdev.score.theme.AmbientColor -import com.cornellappdev.score.theme.CrimsonPrimary -import com.cornellappdev.score.theme.GrayLight -import com.cornellappdev.score.theme.SpotColor -import com.cornellappdev.score.theme.Style.bodyMedium import com.cornellappdev.score.theme.Style.heading2 -import com.cornellappdev.score.theme.White @Composable fun NavigationHeader(title: String, onBackPressed: () -> Unit) { - Box(modifier = Modifier - .shadow(elevation=8.dp, clip = false, spotColor = Color.Black.copy(0.05f)) - .background(Color.White)) { - Box(modifier = Modifier - .padding(start=24.dp, top=56.dp, bottom=12.dp, end=24.dp) - .background(Color.White) - .fillMaxWidth() - .height(27.dp)) { - IconButton(onClick = onBackPressed) { - Icon( - painter = painterResource(id = R.drawable.ic_left_arrowhead), - contentDescription = "Back button", - modifier = Modifier - .width(24.dp) - .height(24.dp), - ) - } - Text(text = title, modifier = Modifier.align(Alignment.Center), style = heading2, color = Color.Black) - } - } + Box( + modifier = Modifier +// .shadow(elevation=8.dp, clip = false, spotColor = Color.Black.copy(0.05f)) todo: what is this for + .background(Color.White) + ) { + Box( + modifier = Modifier + .padding(start = 24.dp, bottom = 12.dp, end = 24.dp) + .background(Color.White) + .fillMaxWidth() + .height(27.dp) + ) { + IconButton(onClick = onBackPressed) { + Icon( + painter = painterResource(id = R.drawable.ic_left_arrowhead), + contentDescription = "Back button", + modifier = Modifier + .width(24.dp) + .height(24.dp), + ) + } + Text( + text = title, + modifier = Modifier.align(Alignment.Center), + style = heading2, + color = Color.Black + ) + } + } } @Preview @Composable private fun NavigationHeaderPreview() { - NavigationHeader("Game Details", {}) + NavigationHeader("Game Details", {}) } \ No newline at end of file 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 2173b0a..e1d4812 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/GameDetailsScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/GameDetailsScreen.kt @@ -22,7 +22,9 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel import com.cornellappdev.score.R import com.cornellappdev.score.components.BoxScore @@ -93,7 +95,7 @@ private fun GameDetailsContent( .background(White) .fillMaxSize(), ) { - Box(modifier = Modifier.height(27.dp)) +// Box(modifier = Modifier.height(27.dp)) GameScoreHeader( leftTeamLogo = painterResource(R.drawable.cornell_logo), rightTeamLogo = gameCard.opponentLogo, @@ -115,7 +117,7 @@ private fun GameDetailsContent( text = gameCard.title, style = heading1.copy(color = GrayPrimary) ) - Spacer(modifier = Modifier.height(12.dp)) + Spacer(modifier = Modifier.height(13.5.dp)) Row(verticalAlignment = Alignment.CenterVertically) { Image( @@ -141,12 +143,20 @@ private fun GameDetailsContent( Text(text = gameCard.dateString, style = bodyNormal.copy(color = GrayPrimary)) } - //render the below if the game is in the future + // render the below if the game is in the future + // TODO: MESSY, is it every the case when there is a boxscore but no scoring summary if (gameCard.isPastStartTime) { if (!gameCard.scoreBreakdown.isNullOrEmpty()) { + Spacer(modifier = Modifier.height(24.dp)) BoxScore(gameCard.gameData) + Spacer(modifier = Modifier.height(24.dp)) } if (gameCard.boxScore.isNotEmpty()) { + Text( + "Scoring Summary", fontSize = 18.sp, + fontWeight = FontWeight.Medium, + ) // TODO: NAVIGATION + Spacer(modifier = Modifier.height(16.dp)) ScoringSummary(gameCard.scoreEvent) } else { Text("No Scoring Summary") // TODO: Make state when there are no scores @@ -168,7 +178,7 @@ private fun GameDetailsContent( "Add to Calendar", painterResource(R.drawable.ic_calendar), onClick = { addToCalendar(context = context, gameCard) } - ) //TODO polish calendar + ) } } From d57b25842137c2eac9812369ca9b49e22e2862af Mon Sep 17 00:00:00 2001 From: Emil Jiang Date: Wed, 9 Apr 2025 01:02:04 -0400 Subject: [PATCH 11/19] minor fix --- app/src/main/java/com/cornellappdev/score/util/CalendarUtil.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/cornellappdev/score/util/CalendarUtil.kt b/app/src/main/java/com/cornellappdev/score/util/CalendarUtil.kt index a5a4f48..b94f7b6 100644 --- a/app/src/main/java/com/cornellappdev/score/util/CalendarUtil.kt +++ b/app/src/main/java/com/cornellappdev/score/util/CalendarUtil.kt @@ -17,7 +17,7 @@ fun addToCalendar(context: Context, details: DetailsCardData) { .toInstant() .toEpochMilli() - val endMillis = startMillis + (2 * 60 * 60 * 1000) // 2 hours later + val endMillis = startMillis + (2 * 60 * 60 * 1000) val intent = Intent(Intent.ACTION_INSERT).apply { data = CalendarContract.Events.CONTENT_URI From c26c0d4de4d58a079617a22ed268a143f01dfbe1 Mon Sep 17 00:00:00 2001 From: Emil Jiang Date: Wed, 9 Apr 2025 01:09:11 -0400 Subject: [PATCH 12/19] todo --- app/src/main/java/com/cornellappdev/score/util/GameDataUtil.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/com/cornellappdev/score/util/GameDataUtil.kt b/app/src/main/java/com/cornellappdev/score/util/GameDataUtil.kt index 75956f6..24ee974 100644 --- a/app/src/main/java/com/cornellappdev/score/util/GameDataUtil.kt +++ b/app/src/main/java/com/cornellappdev/score/util/GameDataUtil.kt @@ -4,6 +4,7 @@ import com.cornellappdev.score.model.GameData import com.cornellappdev.score.model.TeamBoxScore import com.cornellappdev.score.model.TeamScore +// TODO: ASK ABOUT OT fun convertScores(scoreList: List?, sport: String): Pair, Int?> { if (scoreList == null || scoreList.size < 2) return Pair(emptyList(), null) From 739c73dfffba9632ac7f9afef0a92667e7132f37 Mon Sep 17 00:00:00 2001 From: Emil Jiang Date: Mon, 14 Apr 2025 00:39:11 -0400 Subject: [PATCH 13/19] pr changes --- .../score/model/ScoreRepository.kt | 2 +- .../score/nav/root/RootNavigation.kt | 2 +- .../score/screen/GameDetailsScreen.kt | 30 +++++++----- .../cornellappdev/score/util/CalendarUtil.kt | 42 ++++++++++++----- .../com/cornellappdev/score/util/DateUtil.kt | 26 +++++----- .../cornellappdev/score/util/GameDataUtil.kt | 47 ++++++++++++++++--- 6 files changed, 104 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt b/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt index 849a1ce..9fd86fe 100644 --- a/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt +++ b/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt @@ -84,7 +84,7 @@ class ScoreRepository @Inject constructor( } /** - * Asynchronously fetches game details for a particular game. Once finished, will send down + * Asynchronously fetches game details for a particular game. Once finished, will update * `currentGamesFlow` to be observed. */ fun getGameById(id: String) = appScope.launch { 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 3429fb4..a85167c 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 @@ -83,7 +83,7 @@ fun RootNavigation( ) { composable { HomeScreen(navigateToGameDetails = { - navController.navigate(ScoreRootScreens.GameDetailsPage("67e585b941adb1bafec37766")) + navController.navigate(ScoreRootScreens.GameDetailsPage("67e585b941adb1bafec37771")) }) } 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 e1d4812..8df051f 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/GameDetailsScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/GameDetailsScreen.kt @@ -10,19 +10,18 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel @@ -38,9 +37,11 @@ import com.cornellappdev.score.theme.GrayMedium import com.cornellappdev.score.theme.GrayPrimary import com.cornellappdev.score.theme.Style.bodyNormal 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 @Composable @@ -48,7 +49,7 @@ fun GameDetailsScreen( gameDetailsViewModel: GameDetailsViewModel = hiltViewModel(), onBackArrow: () -> Unit = {} ) { - val uiState by gameDetailsViewModel.uiStateFlow.collectAsState() + val uiState = gameDetailsViewModel.collectUiStateValue() Column( modifier = Modifier .fillMaxSize() @@ -95,14 +96,14 @@ private fun GameDetailsContent( .background(White) .fillMaxSize(), ) { -// Box(modifier = Modifier.height(27.dp)) + GameScoreHeader( leftTeamLogo = painterResource(R.drawable.cornell_logo), rightTeamLogo = gameCard.opponentLogo, gradientColor1 = Color(0xFFE1A69F), gradientColor2 = gameCard.opponentColor, leftScore = gameCard.homeScore, - rightScore = gameCard.oppScore, //TODO Score + rightScore = gameCard.oppScore, modifier = Modifier.height(185.dp) ) @@ -131,13 +132,12 @@ private fun GameDetailsContent( Spacer(modifier = Modifier.width(4.dp)) Text(text = gameCard.locationString, style = bodyNormal.copy(color = GrayPrimary)) Spacer(modifier = Modifier.width(12.dp)) - Image( + Icon( painter = painterResource(id = R.drawable.ic_time), contentDescription = "Time Icon", modifier = Modifier - .width(24.dp) - .height(24.dp), - colorFilter = ColorFilter.tint(GrayMedium) + .size(24.dp), + tint = GrayMedium ) Spacer(modifier = Modifier.width(4.dp)) Text(text = gameCard.dateString, style = bodyNormal.copy(color = GrayPrimary)) @@ -146,7 +146,7 @@ private fun GameDetailsContent( // render the below if the game is in the future // TODO: MESSY, is it every the case when there is a boxscore but no scoring summary if (gameCard.isPastStartTime) { - if (!gameCard.scoreBreakdown.isNullOrEmpty()) { + if (gameCard.scoreBreakdown?.isNotEmpty() == true) { Spacer(modifier = Modifier.height(24.dp)) BoxScore(gameCard.gameData) Spacer(modifier = Modifier.height(24.dp)) @@ -154,7 +154,7 @@ private fun GameDetailsContent( if (gameCard.boxScore.isNotEmpty()) { Text( "Scoring Summary", fontSize = 18.sp, - fontWeight = FontWeight.Medium, + style = heading2, ) // TODO: NAVIGATION Spacer(modifier = Modifier.height(16.dp)) ScoringSummary(gameCard.scoreEvent) @@ -177,7 +177,11 @@ private fun GameDetailsContent( ButtonPrimary( "Add to Calendar", painterResource(R.drawable.ic_calendar), - onClick = { addToCalendar(context = context, gameCard) } + onClick = { + gameCard.toCalendarEvent()?.let { event -> + addToCalendar(context = context, event) + } + } ) } diff --git a/app/src/main/java/com/cornellappdev/score/util/CalendarUtil.kt b/app/src/main/java/com/cornellappdev/score/util/CalendarUtil.kt index b94f7b6..24b02f1 100644 --- a/app/src/main/java/com/cornellappdev/score/util/CalendarUtil.kt +++ b/app/src/main/java/com/cornellappdev/score/util/CalendarUtil.kt @@ -6,29 +6,47 @@ import android.provider.CalendarContract import com.cornellappdev.score.model.DetailsCardData import java.time.ZoneId -fun addToCalendar(context: Context, details: DetailsCardData) { - val startDateTime = details.date?.atTime( - details.time.split(":").getOrNull(0)?.toIntOrNull() ?: 0, - details.time.split(":").getOrNull(1)?.toIntOrNull() ?: 0 - ) ?: return +data class CalendarEvent( + val title: String, + val description: String?, + val location: String?, + val date: java.time.LocalDate, + val time: String +) + +fun addToCalendar(context: Context, event: CalendarEvent) { + val startDateTime = event.date.atTime( + event.time.split(":").getOrNull(0)?.toIntOrNull() ?: 0, + event.time.split(":").getOrNull(1)?.toIntOrNull() ?: 0 + ) val startMillis = startDateTime .atZone(ZoneId.systemDefault()) .toInstant() .toEpochMilli() - val endMillis = startMillis + (2 * 60 * 60 * 1000) + val endMillis = startMillis + (2 * 60 * 60 * 1000) // Default 2-hour duration val intent = Intent(Intent.ACTION_INSERT).apply { data = CalendarContract.Events.CONTENT_URI - putExtra(CalendarContract.Events.TITLE, "Cornell vs. ${details.opponent}") - putExtra(CalendarContract.Events.EVENT_LOCATION, details.locationString) - putExtra(CalendarContract.Events.DESCRIPTION, "${details.sport} game (${details.gender})") + putExtra(CalendarContract.Events.TITLE, event.title) + putExtra(CalendarContract.Events.EVENT_LOCATION, event.location) + putExtra(CalendarContract.Events.DESCRIPTION, event.description) putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, startMillis) putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endMillis) } - if (intent.resolveActivity(context.packageManager) != null) { - context.startActivity(intent) - } + context.startActivity(intent) } + +fun DetailsCardData.toCalendarEvent(): CalendarEvent? { + val date = this.date ?: return null + + return CalendarEvent( + title = "Cornell vs. ${this.opponent}", + description = "${this.sport} game (${this.gender})", + location = this.locationString, + date = date, + time = this.time + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/score/util/DateUtil.kt b/app/src/main/java/com/cornellappdev/score/util/DateUtil.kt index c114a25..4b5818b 100644 --- a/app/src/main/java/com/cornellappdev/score/util/DateUtil.kt +++ b/app/src/main/java/com/cornellappdev/score/util/DateUtil.kt @@ -28,14 +28,11 @@ fun parseDateOrNull(strDate: String): LocalDate? { val outputFormatter = DateTimeFormatter.ofPattern("M/d/yyyy") /** - * Parses a date and time string into a [LocalDateTime] object. + * Parses a date and time string into a LocalDateTime object. * - * Expected input format: - * - [date]: String of form "MMM d (EEE)". For example, "Apr 5 (Sat)" - * - [time]: String of form "h:mm a" with or without periods. For example, "3:45 PM" or "3:45 p.m." - * - * The current year is assumed in the final parsed result. - * returns [LocalDateTime] object if parsing succeeds, or [null] if the format is invalid. + * @param date the date string to parse, in the format "MMM d (EEE)" + * @param time the time string to parse, in the format "h:mm a", with or without periods + * @return a LocalDateTime object if parsing succeeds, or null if the format is invalid */ fun parseDateTimeOrNull(date: String, time: String): LocalDateTime? { val subDate = date.substringBefore(" (") + date.substringAfter(")") @@ -59,9 +56,9 @@ fun parseDateTimeOrNull(date: String, time: String): LocalDateTime? { /** * Formats a date and time string into a user-friendly display string. * - * Combines [date] and [time] inputs and attempts to parse them using [parseDateTimeOrNull]. - * If parsing succeeds, returns a string in the format "MMM d, h:mma" (For example, "Apr 5, 3:45PM"). - * If parsing fails, returns an empty string. + * @param date the date string to parse, in the format "MMM d (EEE)" + * @param time the time string to parse, in the format "h:mm a", with or without periods + * @return a formatted date-time string in the format "MMM d, h:mma", or an empty string if parsing fails */ fun formatDateTimeDisplay(date: String, time: String): String { val dateTime = parseDateTimeOrNull(date, time) @@ -70,6 +67,13 @@ fun formatDateTimeDisplay(date: String, time: String): String { return dateTime?.format(formatter) ?: "" } +/** + * Calculates the time remaining until a specified start date and time. + * + * @param date the date string to parse, in the format "MMM d (EEE)" + * @param time the time string to parse, in the format "h:mm a", with or without periods + * @return a pair of (days, hours) until the start time, or null if parsing fails or the time is in the past + */ fun getTimeUntilStart(date: String, time: String): Pair? { val gameStart = parseDateTimeOrNull(date, time) ?: return null val now = LocalDateTime.now() @@ -79,6 +83,6 @@ fun getTimeUntilStart(date: String, time: String): Pair? { val duration = Duration.between(now, gameStart) val days = duration.toDays().toInt() val hours = duration.minusDays(days.toLong()).toHours().toInt() - return Pair(days, hours) + return days to hours } diff --git a/app/src/main/java/com/cornellappdev/score/util/GameDataUtil.kt b/app/src/main/java/com/cornellappdev/score/util/GameDataUtil.kt index 24ee974..985b9b5 100644 --- a/app/src/main/java/com/cornellappdev/score/util/GameDataUtil.kt +++ b/app/src/main/java/com/cornellappdev/score/util/GameDataUtil.kt @@ -4,7 +4,19 @@ import com.cornellappdev.score.model.GameData import com.cornellappdev.score.model.TeamBoxScore import com.cornellappdev.score.model.TeamScore -// TODO: ASK ABOUT OT +/** + * Converts a list of score strings into a list of integers by period and a total score. + * + * Handles null values and non-numeric values: + * - Nulls or "X" are 0 + * - If the sport is baseball, only the first 9 periods counted + * - For other sports, the last item in the list is treated as the total score. + * + * @param scoreList the list of score strings, where each item represents a period score and the last item may be the total + * @param sport the sport type, used to apply specific rules + * @return a pair where the first value is a list of parsed period scores and the second is the total score (or null if invalid) + */ +// TODO: ASK ABOUT OT. Other sports might be added. fun convertScores(scoreList: List?, sport: String): Pair, Int?> { if (scoreList == null || scoreList.size < 2) return Pair(emptyList(), null) @@ -28,19 +40,31 @@ fun convertScores(scoreList: List?, sport: String): Pair, Int return Pair(scoresByPeriod, totalScore) } +/** + * Converts score breakdowns and team box scores into a GameData object. + * + * Uses convertScores to parse individual team scores. If a score breakdown is missing or invalid, + * returns empty scores and zero totals. + * + * @param scoreBreakdown a list containing two lists of score strings, one for each team + * @param team1 the first team's box score information + * @param team2 the second team's box score information + * @param sport the sport type, passed through to convertScores + * @return a GameData object containing structured scores for both teams + */ fun toGameData( scoreBreakdown: List?>?, team1: TeamBoxScore, team2: TeamBoxScore, sport: String ): GameData { - val (team1Scores, team1Total) = if (!scoreBreakdown.isNullOrEmpty()) - convertScores(scoreBreakdown[0], sport) - else Pair(emptyList(), null) + val (team1Scores, team1Total) = scoreBreakdown?.getOrNull(0)?.let { + convertScores(it, sport) + } ?: (emptyList() to null) - val (team2Scores, team2Total) = if (scoreBreakdown != null && scoreBreakdown.size > 1) - convertScores(scoreBreakdown[1], sport) - else Pair(emptyList(), null) + val (team2Scores, team2Total) = scoreBreakdown?.getOrNull(1)?.let { + convertScores(it, sport) + } ?: (emptyList() to null) val team1Score = TeamScore(team = team1, scoresByPeriod = team1Scores, totalScore = team1Total ?: 0) @@ -50,6 +74,15 @@ fun toGameData( return GameData(teamScores = Pair(team1Score, team2Score)) } +/** + * Parses a result string into a pair of home and opponent scores. + * + * Expected format: "Some text,HOME-OPP", where HOME and OPP are integers. + * If the format is invalid or parsing fails, returns null. + * + * @param result the result string to parse, e.g., "L,3-2" + * @return a pair of (homeScore, oppScore) if parsing succeeds, or null if the format is invalid + */ fun parseResultScore(result: String?): Pair? { if (result.isNullOrBlank()) return null From c59f6ab68a04a035713781e946503a8d4f5e0dce Mon Sep 17 00:00:00 2001 From: Emil Jiang Date: Mon, 14 Apr 2025 16:46:01 -0400 Subject: [PATCH 14/19] test --- .../java/com/cornellappdev/score/screen/GameDetailsScreen.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 6521354..0857653 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/GameDetailsScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/GameDetailsScreen.kt @@ -3,6 +3,7 @@ package com.cornellappdev.score.screen import ScoringSummary import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -87,7 +88,7 @@ fun GameDetailsScreen( } @Composable -fun GameDetailsScreen(gameCard: DetailsCardData) { +fun GameDetailsContent(gameCard: DetailsCardData) { Column( modifier = Modifier .background(White) From 2d72680b450d13a43643e03e38410976b55be703 Mon Sep 17 00:00:00 2001 From: Emil Jiang Date: Mon, 14 Apr 2025 17:39:52 -0400 Subject: [PATCH 15/19] nav fix --- .../score/components/FeaturedGameCard.kt | 12 +++++++++--- .../com/cornellappdev/score/components/GameCard.kt | 7 +++++-- .../score/components/GamesCarousel.kt | 10 ++++++---- .../cornellappdev/score/components/PastGameCard.kt | 5 +++-- .../java/com/cornellappdev/score/model/Game.kt | 3 +++ .../cornellappdev/score/model/ScoreRepository.kt | 2 ++ .../cornellappdev/score/nav/root/RootNavigation.kt | 12 ++++++------ .../com/cornellappdev/score/screen/HomeScreen.kt | 14 ++++++++------ .../cornellappdev/score/screen/PastGamesScreen.kt | 10 +++++++--- .../cornellappdev/score/util/TestingConstants.kt | 12 ++++++++++-- 10 files changed, 59 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/score/components/FeaturedGameCard.kt b/app/src/main/java/com/cornellappdev/score/components/FeaturedGameCard.kt index ed55c55..166613c 100644 --- a/app/src/main/java/com/cornellappdev/score/components/FeaturedGameCard.kt +++ b/app/src/main/java/com/cornellappdev/score/components/FeaturedGameCard.kt @@ -2,6 +2,7 @@ package com.cornellappdev.score.components import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -123,6 +124,7 @@ private fun FeaturedGameCardPreview() = ScorePreview { @Composable fun FeaturedGameCard( + id: String, leftTeamLogo: Painter, rightTeamLogo: String, team: String, @@ -137,11 +139,12 @@ fun FeaturedGameCard( modifier: Modifier = Modifier, headerModifier: Modifier = Modifier, leftScore: Int? = null, - rightScore: Int? = null + rightScore: Int? = null, + onClick: (String) -> Unit = {} ) { Column( modifier = modifier - .fillMaxWidth() + .fillMaxWidth().clickable { onClick(id) } ) { FeaturedGameHeader( @@ -156,6 +159,7 @@ fun FeaturedGameCard( ) GameCard( + id = id, teamLogo = rightTeamLogo, team = team, date = date, @@ -172,7 +176,8 @@ fun FeaturedGameCard( bottomStart = 16.dp, bottomEnd = 16.dp ) - ) + ), + onClick = onClick ) } } @@ -181,6 +186,7 @@ fun FeaturedGameCard( @Composable private fun GameScheduleScreen() = ScorePreview { FeaturedGameCard( + id = "1", leftTeamLogo = painterResource(R.drawable.cornell_logo), rightTeamLogo = "https://cornellbigred.com/images/logos/penn_200x200.png?width=80&height=80&mode=max",//painterResource(R.drawable.penn_logo), leftScore = 32, diff --git a/app/src/main/java/com/cornellappdev/score/components/GameCard.kt b/app/src/main/java/com/cornellappdev/score/components/GameCard.kt index 7d28d31..8202aa6 100644 --- a/app/src/main/java/com/cornellappdev/score/components/GameCard.kt +++ b/app/src/main/java/com/cornellappdev/score/components/GameCard.kt @@ -48,6 +48,7 @@ import com.cornellappdev.score.theme.saturatedGreen @Composable fun GameCard( + id: String, teamLogo: String, team: String, date: String, @@ -57,7 +58,7 @@ fun GameCard( sportIcon: Painter, topCornerRound: Boolean, modifier: Modifier = Modifier, - onClick: (Boolean) -> Unit = {} + onClick: (String) -> Unit ) { val cardShape = if (topCornerRound) { RoundedCornerShape(16.dp) // Rounded all @@ -89,7 +90,7 @@ fun GameCard( ) } ) - .clickable { onClick(false) } + .clickable { onClick(id) } ) { Column( modifier = Modifier @@ -210,6 +211,7 @@ fun GameCard( private fun GameCardPreview() = ScorePreview { Column { GameCard( + id = "1", teamLogo = "https://cornellbigred.com/images/logos/penn_200x200.png?width=80&height=80&mode=max", //painterResource(id = R.drawable.penn_logo), team = "Penn", date = "5/20/2024", @@ -219,6 +221,7 @@ private fun GameCardPreview() = ScorePreview { sportIcon = painterResource(id = R.drawable.ic_baseball), topCornerRound = false, modifier = Modifier.padding(16.dp), + onClick = {} ) } } \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/score/components/GamesCarousel.kt b/app/src/main/java/com/cornellappdev/score/components/GamesCarousel.kt index 9490152..f4c1110 100644 --- a/app/src/main/java/com/cornellappdev/score/components/GamesCarousel.kt +++ b/app/src/main/java/com/cornellappdev/score/components/GamesCarousel.kt @@ -27,7 +27,6 @@ import com.cornellappdev.score.theme.CrimsonPrimary import com.cornellappdev.score.theme.GrayLight import com.cornellappdev.score.theme.GrayPrimary import com.cornellappdev.score.theme.Style.heading1 -import com.cornellappdev.score.theme.White import com.cornellappdev.score.util.gameList @Composable @@ -60,7 +59,8 @@ fun DotIndicator( fun GamesCarousel( games: List, variant: GamesCarouselVariant, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + onClick: (String) -> Unit, ) { val pagerState = rememberPagerState(pageCount = { games.size }) Column( @@ -81,6 +81,7 @@ fun GamesCarousel( ) { page -> val game = games[page] FeaturedGameCard( + id = game.id, leftTeamLogo = painterResource(R.drawable.cornell_logo), rightTeamLogo = game.teamLogo, team = game.team, @@ -93,7 +94,8 @@ fun GamesCarousel( modifier = Modifier, headerModifier = Modifier, gradientColor1 = CornellRed, - gradientColor2 = game.teamColor + gradientColor2 = game.teamColor, + onClick = onClick ) } @@ -110,5 +112,5 @@ fun GamesCarousel( @Composable @Preview private fun GamesCarouselPreview() = ScorePreview { - GamesCarousel(gameList, GamesCarouselVariant.UPCOMING) + GamesCarousel(gameList, GamesCarouselVariant.UPCOMING, onClick = {}) } \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/score/components/PastGameCard.kt b/app/src/main/java/com/cornellappdev/score/components/PastGameCard.kt index fcc8506..4ef46c1 100644 --- a/app/src/main/java/com/cornellappdev/score/components/PastGameCard.kt +++ b/app/src/main/java/com/cornellappdev/score/components/PastGameCard.kt @@ -47,7 +47,7 @@ import java.time.LocalDate fun PastGameCard( data: GameCardData, modifier: Modifier = Modifier, - onClick: (Boolean) -> Unit = {} + onClick: (String) -> Unit = {} ) { Card( colors = CardDefaults.cardColors(containerColor = Color.White), @@ -58,7 +58,7 @@ fun PastGameCard( Modifier .border(width = 1.dp, color = GrayStroke, RoundedCornerShape(16.dp)) ) - .clickable { onClick(true) } + .clickable { onClick(data.id) } ) { Row( modifier = Modifier @@ -201,6 +201,7 @@ private fun TeamScore( @Composable private fun PastGameCardPreview() = ScorePreview { val gameCard = GameCardData( + id = "1", teamLogo = "https://cornellbigred.com/images/logos/penn_200x200.png?width=80&height=80&mode=max", team = "University of Pennsylvania", teamColor = Color.Red, 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 b7f5cbf..d6dfd42 100644 --- a/app/src/main/java/com/cornellappdev/score/model/Game.kt +++ b/app/src/main/java/com/cornellappdev/score/model/Game.kt @@ -16,6 +16,7 @@ import java.time.LocalDateTime // TODO Refactor to make easier to filter... actual gender, etc. data class Game( + val id: String, val teamName: String, val teamLogo: String, val teamColor: Color, @@ -71,6 +72,7 @@ data class GameDetailsGame( // Data for HomeScreen game displays data class GameCardData( + val id: String, val teamLogo: String, val team: String, val teamColor: Color, @@ -202,6 +204,7 @@ enum class GamesCarouselVariant { fun Game.toGameCardData(): GameCardData { return GameCardData( + id = id, teamLogo = teamLogo, team = teamName, teamColor = teamColor, diff --git a/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt b/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt index 9fd86fe..b20885d 100644 --- a/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt +++ b/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt @@ -60,6 +60,7 @@ class ScoreRepository @Inject constructor( val otherScore = scores?.getOrNull(1)?.toNumberOrNull() game?.team?.image?.let { Game( + id = game.id ?: "", // Should never be null teamLogo = it, teamName = game.team.name, teamColor = parseColor(game.team.color).copy(alpha = 0.4f * 255), @@ -90,6 +91,7 @@ class ScoreRepository @Inject constructor( fun getGameById(id: String) = appScope.launch { _currentGameFlow.value = ApiResponse.Loading try { + Log.d("ScoreRepository", "getGameById: ${id}") val result = (apolloClient.query(GameByIdQuery(id)).execute()).toResult() result.getOrNull()?.game?.let { _currentGameFlow.value = ApiResponse.Success(it.toGameDetails()) 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 773f5ee..ec7a489 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 @@ -106,20 +106,20 @@ fun RootNavigation( ) { composable { HomeScreen(navigateToGameDetails = { - navController.navigate(ScoreRootScreens.GameDetailsPage("")) + navController.navigate(ScoreRootScreens.GameDetailsPage(it)) }) } - composable { - GameDetailsScreen(onBackArrow = { - navController.navigateUp() - }) + composable { + GameDetailsScreen(onBackArrow = { + navController.navigateUp() + }) } composable { PastGamesScreen(navigateToGameDetails = { - navController.navigate(ScoreRootScreens.GameDetailsPage("")) + navController.navigate(ScoreRootScreens.GameDetailsPage(it)) }) } } 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 201485a..36f94e0 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/HomeScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/HomeScreen.kt @@ -11,8 +11,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material3.Button -import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -42,7 +40,7 @@ import com.cornellappdev.score.viewmodel.HomeViewModel @Composable fun HomeScreen( homeViewModel: HomeViewModel = hiltViewModel(), - navigateToGameDetails: (Boolean) -> Unit = {} + navigateToGameDetails: (String) -> Unit = {} ) { val uiState = homeViewModel.collectUiStateValue() @@ -51,7 +49,6 @@ fun HomeScreen( modifier = Modifier .background(Color.White) ) { - Button(onClick = { navigateToGameDetails(true) }) { } when (uiState.loadedState) { is ApiResponse.Loading -> { LoadingScreen("Loading Upcoming...", "Loading Schedules...") @@ -79,11 +76,15 @@ private fun HomeContent( uiState: HomeUiState, onGenderSelected: (GenderDivision) -> Unit, onSportSelected: (SportSelection) -> Unit, - navigateToGameDetails: (Boolean) -> Unit = {} + navigateToGameDetails: (String) -> Unit = {} ) { LazyColumn(contentPadding = PaddingValues(top = 24.dp, start = 24.dp, end = 24.dp)) { item { - GamesCarousel(uiState.upcomingGames, GamesCarouselVariant.UPCOMING) + GamesCarousel( + uiState.upcomingGames, + GamesCarouselVariant.UPCOMING, + onClick = navigateToGameDetails + ) } stickyHeader { Column(modifier = Modifier.background(White)) { @@ -110,6 +111,7 @@ private fun HomeContent( items(uiState.filteredGames) { val game = it GameCard( + id = game.id, teamLogo = game.teamLogo, team = game.team, date = game.dateString, 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 8d36258..b4b052b 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/PastGamesScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/PastGamesScreen.kt @@ -38,7 +38,7 @@ import com.cornellappdev.score.viewmodel.PastGamesViewModel @Composable fun PastGamesScreen( pastGamesViewModel: PastGamesViewModel = hiltViewModel(), - navigateToGameDetails: (Boolean) -> Unit = {} + navigateToGameDetails: (String) -> Unit = {} ) { val uiState = pastGamesViewModel.collectUiStateValue() @@ -74,11 +74,15 @@ private fun PastGamesContent( uiState: PastGamesUiState, onGenderSelected: (GenderDivision) -> Unit, onSportSelected: (SportSelection) -> Unit, - navigateToGameDetails: (Boolean) -> Unit = {} + navigateToGameDetails: (String) -> Unit = {} ) { LazyColumn(contentPadding = PaddingValues(top = 24.dp, start = 24.dp, end = 24.dp)) { item { - GamesCarousel(uiState.pastGames, GamesCarouselVariant.PAST) + GamesCarousel( + uiState.pastGames, + GamesCarouselVariant.PAST, + onClick = navigateToGameDetails + ) } stickyHeader { Column(modifier = Modifier.background(White)) { diff --git a/app/src/main/java/com/cornellappdev/score/util/TestingConstants.kt b/app/src/main/java/com/cornellappdev/score/util/TestingConstants.kt index 5f2ee12..7e31dd3 100644 --- a/app/src/main/java/com/cornellappdev/score/util/TestingConstants.kt +++ b/app/src/main/java/com/cornellappdev/score/util/TestingConstants.kt @@ -13,6 +13,7 @@ import com.cornellappdev.score.model.TeamScore import java.time.LocalDate val PENN_GAME = GameCardData( + id = "1", teamLogo = "https://cornellbigred.com/images/logos/penn_200x200.png?width=80&height=80&mode=max", team = "Penn", teamColor = Color(0x66B31B1B), @@ -28,6 +29,7 @@ val PENN_GAME = GameCardData( ) val PRINCETON_GAME = GameCardData( + id = "1", teamLogo = "https://cornellbigred.com/images/logos/Princeton_Tigers.png?width=80&height=80&mode=max", team = "Princeton", teamColor = Color(0x66FF6000), @@ -68,8 +70,14 @@ val teamScore2 = TeamScore( val gameData = GameData(teamScores = Pair(teamScore1, teamScore2)) -val team3 = TeamGameSummary(name = "Cornell", "https://cornellbigred.com/images/logos/penn_200x200.png?width=80&height=80&mode=max") -val team4 = TeamGameSummary(name = "Yale", "https://cornellbigred.com/images/logos/penn_200x200.png?width=80&height=80&mode=max") +val team3 = TeamGameSummary( + name = "Cornell", + "https://cornellbigred.com/images/logos/penn_200x200.png?width=80&height=80&mode=max" +) +val team4 = TeamGameSummary( + name = "Yale", + "https://cornellbigred.com/images/logos/penn_200x200.png?width=80&height=80&mode=max" +) val scoreEvents1 = listOf( ScoreEvent( id = 1, From 8c3264b22d807ac5a41a508f226a4f75fd36ad18 Mon Sep 17 00:00:00 2001 From: Emil Jiang Date: Mon, 14 Apr 2025 18:02:12 -0400 Subject: [PATCH 16/19] center button and fix padding --- .../score/screen/GameDetailsScreen.kt | 44 +++++++++++-------- 1 file changed, 26 insertions(+), 18 deletions(-) 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 0857653..cde9179 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/GameDetailsScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/GameDetailsScreen.kt @@ -8,6 +8,7 @@ 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.size @@ -161,28 +162,35 @@ fun GameDetailsContent(gameCard: DetailsCardData) { Text("No Scoring Summary") // TODO: Make state when there are no scores } } else { - Spacer(modifier = Modifier.height(40.dp)) - if (gameCard.daysUntilGame != null && gameCard.hoursUntilGame != null) { - TimeUntilStartCard( - gameCard.daysUntilGame, - gameCard.hoursUntilGame + val context = LocalContext.current + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(modifier = Modifier.height(40.dp)) + + if (gameCard.daysUntilGame != null && gameCard.hoursUntilGame != null) { + TimeUntilStartCard( + gameCard.daysUntilGame, + gameCard.hoursUntilGame + ) + } + + Spacer(modifier = Modifier.weight(1f)) + + ButtonPrimary( + "Add to Calendar", + painterResource(R.drawable.ic_calendar), + onClick = { + gameCard.toCalendarEvent()?.let { event -> + addToCalendar(context = context, event) + } + } ) } + } } - if (!gameCard.isPastStartTime) { - val context = LocalContext.current - Spacer(modifier = Modifier.height(84.dp)) - ButtonPrimary( - "Add to Calendar", - painterResource(R.drawable.ic_calendar), - onClick = { - gameCard.toCalendarEvent()?.let { event -> - addToCalendar(context = context, event) - } - } - ) - } } } From 251217971a0cdaa997f4fa9c5c9f2dae19c59926 Mon Sep 17 00:00:00 2001 From: zachseidner1 Date: Mon, 14 Apr 2025 20:11:54 -0400 Subject: [PATCH 17/19] Add preview, fix small score box thing, navigation bar not shown when on game details --- .../score/components/NavigationHeader.kt | 2 +- .../score/components/ScoreBox.kt | 3 +- .../score/nav/root/RootNavigation.kt | 3 + .../score/screen/GameDetailsScreen.kt | 108 +++++++++++++++++- 4 files changed, 113 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/score/components/NavigationHeader.kt b/app/src/main/java/com/cornellappdev/score/components/NavigationHeader.kt index 41d5263..452679a 100644 --- a/app/src/main/java/com/cornellappdev/score/components/NavigationHeader.kt +++ b/app/src/main/java/com/cornellappdev/score/components/NavigationHeader.kt @@ -28,7 +28,7 @@ fun NavigationHeader(title: String, onBackPressed: () -> Unit) { ) { Box( modifier = Modifier - .padding(start = 24.dp, top = 56.dp, bottom = 12.dp, end = 24.dp) + .padding(start = 24.dp, top = 24.dp, bottom = 12.dp, end = 24.dp) .background(Color.White) .fillMaxWidth() .height(27.dp) 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 3cab34a..31291ff 100644 --- a/app/src/main/java/com/cornellappdev/score/components/ScoreBox.kt +++ b/app/src/main/java/com/cornellappdev/score/components/ScoreBox.kt @@ -102,7 +102,8 @@ fun TeamScoreRow(teamScore: TeamScore, totalTextColor: Color) { style = bodyNormal, color = GrayPrimary, modifier = Modifier.weight(1f), - textAlign = TextAlign.Center + textAlign = TextAlign.Center, + maxLines = 1, ) teamScore.scoresByPeriod.forEach { score -> 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 ec7a489..a3c8abc 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 @@ -68,6 +68,9 @@ fun RootNavigation( } Scaffold(modifier = Modifier.fillMaxSize(), bottomBar = { + if (navBackStackEntry?.toScreen() is ScoreRootScreens.GameDetailsPage) { + return@Scaffold + } NavigationBar(containerColor = White) { tabs.map { item -> val isSelected = item.screen == navBackStackEntry?.toScreen() 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 cde9179..554185d 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/GameDetailsScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/GameDetailsScreen.kt @@ -23,6 +23,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel @@ -34,6 +35,12 @@ import com.cornellappdev.score.components.NavigationHeader import com.cornellappdev.score.components.TimeUntilStartCard import com.cornellappdev.score.model.ApiResponse import com.cornellappdev.score.model.DetailsCardData +import com.cornellappdev.score.model.GameData +import com.cornellappdev.score.model.GameDetailsBoxScore +import com.cornellappdev.score.model.ScoreEvent +import com.cornellappdev.score.model.TeamBoxScore +import com.cornellappdev.score.model.TeamGameSummary +import com.cornellappdev.score.model.TeamScore import com.cornellappdev.score.theme.GrayMedium import com.cornellappdev.score.theme.GrayPrimary import com.cornellappdev.score.theme.Style.bodyNormal @@ -44,6 +51,7 @@ 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( @@ -191,6 +199,104 @@ fun GameDetailsContent(gameCard: DetailsCardData) { } } - } } + +@Preview +@Composable +private fun GameDetailsPreview() { + GameDetailsContent( + DetailsCardData( + title = "Championship Game", + opponentLogo = "https://example.com/logo.png", + opponent = "Wildcats", + opponentColor = Color(0xFF123456), + date = LocalDate.of(2025, 4, 20), + time = "7:30 PM", + dateString = "April 20, 2025", + isPastStartTime = false, + location = "Main Stadium", + locationString = "Main Stadium, Cityville", + gender = "Men's", + genderIcon = 123, // Dummy resource ID + sport = "Basketball", + sportIcon = 456, // Dummy resource ID + boxScore = listOf( + GameDetailsBoxScore( + team = "Tigers", + period = "1st", + time = "12:34", + description = "3-point shot", + scorer = "John Doe", + assist = "Mike Smith", + scoreBy = "Tigers", + corScore = 21, + oppScore = 18 + ), + GameDetailsBoxScore( + team = "Wildcats", + period = "1st", + time = "10:01", + description = "Layup", + scorer = "Jane Roe", + assist = "Tom Lee", + scoreBy = "Wildcats", + corScore = 21, + oppScore = 20 + ) + ), + scoreBreakdown = listOf( + listOf("10", "15", "20", "18"), // Tigers per quarter + listOf("12", "10", "18", "22") // Wildcats per quarter + ), + gameData = GameData( + Pair( + TeamScore( + team = TeamBoxScore( + name = "Tigers", + ), + scoresByPeriod = listOf(20, 18, 22, 18), + totalScore = 78 + ), + TeamScore( + team = TeamBoxScore( + name = "Wildcats", + ), + scoresByPeriod = listOf(18, 20, 16, 21), + totalScore = 75 + ) + ) + ), + scoreEvent = listOf( + ScoreEvent( + id = 1, + time = "11:11", + quarter = "2nd", + team = TeamGameSummary( + name = "Tigers", + logo = "https://example.com/tigers.png" + ), + eventType = "3PT", + score = "36-34", + description = "Three-pointer by John Doe" + ), + ScoreEvent( + id = 2, + time = "08:45", + quarter = "3rd", + team = TeamGameSummary( + name = "Wildcats", + logo = "https://example.com/wildcats.png" + ), + eventType = "FT", + score = "36-35", + description = "Free throw by Jane Roe" + ) + ), + daysUntilGame = 6, + hoursUntilGame = 144, + homeScore = 78, + oppScore = 75 + ) + ) +} From d33706815a61c018a184b9cd84f9e6f5628a3a13 Mon Sep 17 00:00:00 2001 From: zachseidner1 Date: Mon, 14 Apr 2025 20:12:49 -0400 Subject: [PATCH 18/19] Ellipses --- .../main/java/com/cornellappdev/score/components/ScoreBox.kt | 2 ++ 1 file changed, 2 insertions(+) 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 31291ff..b85e757 100644 --- a/app/src/main/java/com/cornellappdev/score/components/ScoreBox.kt +++ b/app/src/main/java/com/cornellappdev/score/components/ScoreBox.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.cornellappdev.score.model.GameData @@ -104,6 +105,7 @@ fun TeamScoreRow(teamScore: TeamScore, totalTextColor: Color) { modifier = Modifier.weight(1f), textAlign = TextAlign.Center, maxLines = 1, + overflow = TextOverflow.Ellipsis ) teamScore.scoresByPeriod.forEach { score -> From a54bdda809f53801e181f54ae8fc175cfd3cb3b4 Mon Sep 17 00:00:00 2001 From: Emil Jiang Date: Mon, 14 Apr 2025 22:00:23 -0400 Subject: [PATCH 19/19] pr --- .../score/components/FeaturedGameCard.kt | 8 ++--- .../score/components/GameCard.kt | 6 ++-- .../score/components/GamesCarousel.kt | 3 +- .../score/components/PastGameCard.kt | 4 +-- .../score/model/ScoreRepository.kt | 1 - .../cornellappdev/score/screen/HomeScreen.kt | 34 ++++++++++--------- .../score/screen/PastGamesScreen.kt | 2 +- .../score/util/TestingConstants.kt | 4 +-- 8 files changed, 29 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/score/components/FeaturedGameCard.kt b/app/src/main/java/com/cornellappdev/score/components/FeaturedGameCard.kt index 166613c..bcb5bc4 100644 --- a/app/src/main/java/com/cornellappdev/score/components/FeaturedGameCard.kt +++ b/app/src/main/java/com/cornellappdev/score/components/FeaturedGameCard.kt @@ -124,7 +124,6 @@ private fun FeaturedGameCardPreview() = ScorePreview { @Composable fun FeaturedGameCard( - id: String, leftTeamLogo: Painter, rightTeamLogo: String, team: String, @@ -140,11 +139,12 @@ fun FeaturedGameCard( headerModifier: Modifier = Modifier, leftScore: Int? = null, rightScore: Int? = null, - onClick: (String) -> Unit = {} + onClick: () -> Unit = {} ) { Column( modifier = modifier - .fillMaxWidth().clickable { onClick(id) } + .fillMaxWidth() + .clickable { onClick() } ) { FeaturedGameHeader( @@ -159,7 +159,6 @@ fun FeaturedGameCard( ) GameCard( - id = id, teamLogo = rightTeamLogo, team = team, date = date, @@ -186,7 +185,6 @@ fun FeaturedGameCard( @Composable private fun GameScheduleScreen() = ScorePreview { FeaturedGameCard( - id = "1", leftTeamLogo = painterResource(R.drawable.cornell_logo), rightTeamLogo = "https://cornellbigred.com/images/logos/penn_200x200.png?width=80&height=80&mode=max",//painterResource(R.drawable.penn_logo), leftScore = 32, diff --git a/app/src/main/java/com/cornellappdev/score/components/GameCard.kt b/app/src/main/java/com/cornellappdev/score/components/GameCard.kt index 8202aa6..7c7da7b 100644 --- a/app/src/main/java/com/cornellappdev/score/components/GameCard.kt +++ b/app/src/main/java/com/cornellappdev/score/components/GameCard.kt @@ -48,7 +48,6 @@ import com.cornellappdev.score.theme.saturatedGreen @Composable fun GameCard( - id: String, teamLogo: String, team: String, date: String, @@ -58,7 +57,7 @@ fun GameCard( sportIcon: Painter, topCornerRound: Boolean, modifier: Modifier = Modifier, - onClick: (String) -> Unit + onClick: () -> Unit ) { val cardShape = if (topCornerRound) { RoundedCornerShape(16.dp) // Rounded all @@ -90,7 +89,7 @@ fun GameCard( ) } ) - .clickable { onClick(id) } + .clickable { onClick() } ) { Column( modifier = Modifier @@ -211,7 +210,6 @@ fun GameCard( private fun GameCardPreview() = ScorePreview { Column { GameCard( - id = "1", teamLogo = "https://cornellbigred.com/images/logos/penn_200x200.png?width=80&height=80&mode=max", //painterResource(id = R.drawable.penn_logo), team = "Penn", date = "5/20/2024", diff --git a/app/src/main/java/com/cornellappdev/score/components/GamesCarousel.kt b/app/src/main/java/com/cornellappdev/score/components/GamesCarousel.kt index 10399b7..b8f8016 100644 --- a/app/src/main/java/com/cornellappdev/score/components/GamesCarousel.kt +++ b/app/src/main/java/com/cornellappdev/score/components/GamesCarousel.kt @@ -72,7 +72,6 @@ fun GamesCarousel( ) { page -> val game = games[page] FeaturedGameCard( - id = game.id, leftTeamLogo = painterResource(R.drawable.cornell_logo), rightTeamLogo = game.teamLogo, team = game.team, @@ -86,7 +85,7 @@ fun GamesCarousel( headerModifier = Modifier, gradientColor1 = CornellRed, gradientColor2 = game.teamColor, - onClick = onClick + onClick = { onClick(game.id) } ) } diff --git a/app/src/main/java/com/cornellappdev/score/components/PastGameCard.kt b/app/src/main/java/com/cornellappdev/score/components/PastGameCard.kt index 4ef46c1..6e09ae5 100644 --- a/app/src/main/java/com/cornellappdev/score/components/PastGameCard.kt +++ b/app/src/main/java/com/cornellappdev/score/components/PastGameCard.kt @@ -47,7 +47,7 @@ import java.time.LocalDate fun PastGameCard( data: GameCardData, modifier: Modifier = Modifier, - onClick: (String) -> Unit = {} + onClick: () -> Unit = {} ) { Card( colors = CardDefaults.cardColors(containerColor = Color.White), @@ -58,7 +58,7 @@ fun PastGameCard( Modifier .border(width = 1.dp, color = GrayStroke, RoundedCornerShape(16.dp)) ) - .clickable { onClick(data.id) } + .clickable { onClick() } ) { Row( modifier = Modifier diff --git a/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt b/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt index b20885d..43cce31 100644 --- a/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt +++ b/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt @@ -91,7 +91,6 @@ class ScoreRepository @Inject constructor( fun getGameById(id: String) = appScope.launch { _currentGameFlow.value = ApiResponse.Loading try { - Log.d("ScoreRepository", "getGameById: ${id}") val result = (apolloClient.query(GameByIdQuery(id)).execute()).toResult() result.getOrNull()?.game?.let { _currentGameFlow.value = ApiResponse.Success(it.toGameDetails()) 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 0d92d87..f2a1918 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/HomeScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/HomeScreen.kt @@ -100,9 +100,11 @@ private fun HomeContent( GamesCarousel(uiState.upcomingGames, navigateToGameDetails) } stickyHeader { - Column(modifier = Modifier - .background(White) - .padding(horizontal = 24.dp)) { + Column( + modifier = Modifier + .background(White) + .padding(horizontal = 24.dp) + ) { Spacer(Modifier.height(24.dp)) Text( text = "Game Schedule", @@ -128,20 +130,20 @@ private fun HomeContent( } items(uiState.filteredGames) { val game = it - Column (modifier = Modifier.padding(horizontal = 24.dp)) { + Column(modifier = Modifier.padding(horizontal = 24.dp)) { GameCard( - id = game.id, - teamLogo = game.teamLogo, - team = game.team, - date = game.dateString, - isLive = game.isLive, - genderIcon = painterResource(game.genderIcon), - sportIcon = painterResource(game.sportIcon), - location = game.location, - topCornerRound = true, - onClick = navigateToGameDetails - ) - Spacer(modifier = Modifier.height(16.dp))} + teamLogo = game.teamLogo, + team = game.team, + date = game.dateString, + isLive = game.isLive, + genderIcon = painterResource(game.genderIcon), + sportIcon = painterResource(game.sportIcon), + location = game.location, + topCornerRound = true, + onClick = { navigateToGameDetails(game.id) } + ) + Spacer(modifier = Modifier.height(16.dp)) + } } } } 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 b7f7b78..e23a8bb 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/PastGamesScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/PastGamesScreen.kt @@ -129,7 +129,7 @@ private fun PastGamesContent( Column (modifier = Modifier.padding(horizontal = 24.dp)) { PastGameCard( data = game, - onClick = navigateToGameDetails + onClick = {navigateToGameDetails(game.id)} ) Spacer(modifier = Modifier.height(16.dp)) } diff --git a/app/src/main/java/com/cornellappdev/score/util/TestingConstants.kt b/app/src/main/java/com/cornellappdev/score/util/TestingConstants.kt index 7e31dd3..02cd1b8 100644 --- a/app/src/main/java/com/cornellappdev/score/util/TestingConstants.kt +++ b/app/src/main/java/com/cornellappdev/score/util/TestingConstants.kt @@ -13,7 +13,7 @@ import com.cornellappdev.score.model.TeamScore import java.time.LocalDate val PENN_GAME = GameCardData( - id = "1", + id = "", teamLogo = "https://cornellbigred.com/images/logos/penn_200x200.png?width=80&height=80&mode=max", team = "Penn", teamColor = Color(0x66B31B1B), @@ -29,7 +29,7 @@ val PENN_GAME = GameCardData( ) val PRINCETON_GAME = GameCardData( - id = "1", + id = "", teamLogo = "https://cornellbigred.com/images/logos/Princeton_Tigers.png?width=80&height=80&mode=max", team = "Princeton", teamColor = Color(0x66FF6000),