Skip to content
4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ if (secretsPropertiesFile.exists()) {
plugins {
alias(libs.plugins.androidApplication)
alias(libs.plugins.jetbrainsKotlinAndroid) version "1.9.10"
alias(libs.plugins.apollo)
id("com.apollographql.apollo") version "4.0.0"
id("kotlin-kapt")
id("com.google.dagger.hilt.android")
id("org.jetbrains.kotlin.plugin.compose") version "2.0.0" // this version matches your Kotlin version
Expand Down Expand Up @@ -97,7 +97,7 @@ dependencies {
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.1")
implementation(libs.apollo.runtime)
implementation("com.apollographql.apollo:apollo-runtime:4.0.0")
implementation("io.coil-kt.coil3:coil-compose:3.1.0")
implementation("io.coil-kt.coil3:coil-network-okhttp:3.1.0")
lintChecks(libs.compose.lint.checks)
Expand Down
1 change: 1 addition & 0 deletions app/src/main/graphql/Games.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ query Games{
games{
id
date
time
city
sport
team{
Expand Down
16 changes: 11 additions & 5 deletions app/src/main/java/com/cornellappdev/score/model/Game.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ data class Game(
val id: String,
val teamName: String,
val teamLogo: String,
val time: String?,
val teamColor: Color,
val gender: String,
val sport: String,
Expand Down Expand Up @@ -146,7 +147,7 @@ data class TeamScore(
// Aggregated game data showing scores for both teams
data class GameData(
val teamScores: Pair<TeamScore, TeamScore>
){
) {
val maxPeriods: Int
get() =
maxOf(
Expand Down Expand Up @@ -250,7 +251,11 @@ fun Game.toGameCardData(): GameCardData {
date = parseDateOrNull(date),
dateString = parseDateOrNull(date)?.format(outputFormatter)
?: date,
isLive = (LocalDate.now() == parseDateOrNull(date)),
isLive = parseDateTimeOrNull(date, time ?: "")?.let { startTime ->
val now = LocalDateTime.now()
val endTime = startTime.plusHours(2)
now.isAfter(startTime) && now.isBefore(endTime)
} ?: false,
Comment on lines +254 to +258
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Hardcoded 2-hour game duration may not fit all sports.

The isLive logic assumes all games last 2 hours. However, game durations vary significantly by sport:

  • Baseball: ~3 hours
  • Football: ~3 hours
  • Basketball: ~2 hours
  • Ice Hockey: ~2.5 hours

This could cause games to incorrectly show as "not live" while still in progress. Consider either:

  1. Making the duration sport-specific
  2. Using a longer default (e.g., 3.5 hours) to be safe
  3. Using the game's actual status from the backend if available
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/cornellappdev/score/model/Game.kt` around lines 254 -
258, The isLive computation in Game.kt currently uses a hardcoded 2-hour window
via parseDateTimeOrNull(date, time ?: "") and startTime.plusHours(2), which
misrepresents many sports; update isLive to compute endTime using a sport-aware
duration (e.g., call a new helper getDurationForSport(sport: String): Duration
or use an existing sportDurationMinutes field) and fall back to a safer default
like 3.5 hours (Duration.ofMinutes(210)) if no sport-specific value exists;
ensure you reference the game's sport identifier (e.g., sport or league
property) when selecting the duration and keep parseDateTimeOrNull(date, time ?:
"") as the start time source.

isPast = isPast,
location = city,
gender = gender,
Expand Down Expand Up @@ -290,14 +295,15 @@ fun GameDetailsGame.toGameCardData(): DetailsCardData {
scoreBreakdown = scoreBreakdown,
team1 = TeamBoxScore("Cornell"),
team2 = TeamBoxScore(team?.name ?: ""),
sport = sport
sport = sport,
result = result ?: ""
),
scoreEvent = boxScore?.toScoreEvents(team?.image ?: "") ?: emptyList(),
daysUntilGame = daysUntil,
hoursUntilGame = hoursUntil,
homeScore = convertScores(scoreBreakdown?.getOrNull(0), sport).second
homeScore = convertScores(scoreBreakdown?.getOrNull(0), sport, result ?: "").second
?: parsedScores?.first ?: 0,
oppScore = convertScores(scoreBreakdown?.getOrNull(1), sport).second
oppScore = convertScores(scoreBreakdown?.getOrNull(1), sport, result ?: "").second
?: parsedScores?.second ?: 0
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.util.Log
import com.apollographql.apollo.ApolloClient
import com.cornellappdev.score.util.isValidSport
import com.cornellappdev.score.util.parseColor
import com.cornellappdev.score.util.parseResultScore
import com.example.score.GameByIdQuery
import com.example.score.GamesQuery
import com.example.score.PagedGamesQuery
Expand Down Expand Up @@ -75,6 +76,7 @@ class ScoreRepository @Inject constructor(
id = game.id ?: "", // Should never be null
teamLogo = it,
teamName = game.team.name,
time = game.time,
teamColor = parseColor(game.team.color).copy(alpha = 0.4f * 255),
gender = if (game.gender == "Mens") "Men's" else "Women's",
sport = game.sport,
Expand Down Expand Up @@ -133,11 +135,15 @@ class ScoreRepository @Inject constructor(
.mapNotNull { graphqlGame ->
val scores = graphqlGame.result?.split(",")?.getOrNull(1)?.split("-")
val cornellScore = scores?.getOrNull(0)?.toNumberOrNull()
val otherScore = scores?.getOrNull(1)?.toNumberOrNull()
?: parseResultScore(graphqlGame.result)?.first
val otherScore = scores?.getOrNull(1)?.toNumberOrNull() ?: parseResultScore(
graphqlGame.result
)?.second
graphqlGame.team?.image?.let { imageUrl ->
Game(
id = graphqlGame.id ?: "",
teamLogo = imageUrl,
time = graphqlGame.time,
teamName = graphqlGame.team.name,
teamColor = parseColor(graphqlGame.team.color).copy(alpha = 0.4f * 255),
gender = if (graphqlGame.gender == "Mens") "Men's" else "Women's",
Expand Down Expand Up @@ -171,6 +177,7 @@ class ScoreRepository @Inject constructor(
* `currentGamesFlow` to be observed.
*/
fun getGameById(id: String) = appScope.launch {
Log.d("ScoreRepository", "Fetching game with id: $id")
_currentGameFlow.value = ApiResponse.Loading
try {
val result =
Expand All @@ -181,6 +188,7 @@ class ScoreRepository @Inject constructor(

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)
Expand Down
22 changes: 15 additions & 7 deletions app/src/main/java/com/cornellappdev/score/util/GameDataUtil.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.cornellappdev.score.util

import android.util.Log
import com.cornellappdev.score.model.GameData
import com.cornellappdev.score.model.TeamBoxScore
import com.cornellappdev.score.model.TeamScore
Expand All @@ -17,7 +18,7 @@ import com.cornellappdev.score.model.TeamScore
* @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<String?>?, sport: String): Pair<List<Int>, Int?> {
fun convertScores(scoreList: List<String?>?, sport: String, result: String): Pair<List<Int>, Int?> {
if (scoreList == null || scoreList.size < 2) return Pair(emptyList(), null)

var scoresByPeriod = scoreList
Expand All @@ -31,7 +32,12 @@ fun convertScores(scoreList: List<String?>?, sport: String): Pair<List<Int>, Int
}

if (sport.lowercase() == "baseball") {
scoresByPeriod = scoresByPeriod.take(9)
val scoreParsed = result.split("(")
scoresByPeriod = if (scoreParsed.size > 1) {
scoresByPeriod.take(6)
} else {
scoresByPeriod.take(9)
}
val totalScore = scoresByPeriod.sum()
return Pair(scoresByPeriod, totalScore)
}
Expand All @@ -56,14 +62,15 @@ fun toGameData(
scoreBreakdown: List<List<String?>?>?,
team1: TeamBoxScore,
team2: TeamBoxScore,
sport: String
sport: String,
result: String,
): GameData {
val (team1Scores, team1Total) = scoreBreakdown?.getOrNull(0)?.let {
convertScores(it, sport)
convertScores(it, sport, result)
} ?: (emptyList<Int>() to null)

val (team2Scores, team2Total) = scoreBreakdown?.getOrNull(1)?.let {
convertScores(it, sport)
convertScores(it, sport, result)
} ?: (emptyList<Int>() to null)

val team1Score =
Expand All @@ -90,11 +97,12 @@ fun parseResultScore(result: String?): Pair<Int, Int>? {
if (parts.size != 2) return null

val scorePart = parts[1].split("-")
val secondScorePartEdge = scorePart[1].split("(")
if (scorePart.size != 2) return null

val homeScore = scorePart[0].toIntOrNull()
val oppScore = scorePart[1].toIntOrNull()

val oppScore = secondScorePartEdge[0].toIntOrNull()
Log.d("HIHI", oppScore.toString())
Comment on lines 99 to +105
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential IndexOutOfBoundsException and debug log to remove.

Two issues here:

  1. Line 100 accesses scorePart[1] before the size check on line 101. If scorePart has fewer than 2 elements, this will throw an IndexOutOfBoundsException.

  2. Line 105 contains a debug log with tag "HIHI" that should be removed before merging.

🐛 Proposed fix
     val scorePart = parts[1].split("-")
+    if (scorePart.size != 2) return null
+
     val secondScorePartEdge = scorePart[1].split("(")
-    if (scorePart.size != 2) return null

     val homeScore = scorePart[0].toIntOrNull()
     val oppScore = secondScorePartEdge[0].toIntOrNull()
-    Log.d("HIHI", oppScore.toString())
     if (homeScore != null && oppScore != null) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
val scorePart = parts[1].split("-")
val secondScorePartEdge = scorePart[1].split("(")
if (scorePart.size != 2) return null
val homeScore = scorePart[0].toIntOrNull()
val oppScore = scorePart[1].toIntOrNull()
val oppScore = secondScorePartEdge[0].toIntOrNull()
Log.d("HIHI", oppScore.toString())
val scorePart = parts[1].split("-")
if (scorePart.size != 2) return null
val secondScorePartEdge = scorePart[1].split("(")
val homeScore = scorePart[0].toIntOrNull()
val oppScore = secondScorePartEdge[0].toIntOrNull()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/cornellappdev/score/util/GameDataUtil.kt` around lines
99 - 105, The code accesses scorePart[1] before verifying scorePart.size,
risking IndexOutOfBoundsException, and includes a stray debug Log.d("HIHI", ...)
that must be removed; reorder the logic in the parsing block so you check if
scorePart.size != 2 and return null before any access to scorePart[1] (or use
getOrNull) and then compute secondScorePartEdge, homeScore, and oppScore, and
delete the debug Log.d call; refer to the variables scorePart,
secondScorePartEdge, homeScore, and oppScore in GameDataUtil.kt to locate the
exact lines to change.

if (homeScore != null && oppScore != null) {
return Pair(homeScore, oppScore)
} else {
Expand Down
Loading