Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 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 @@ -67,6 +67,9 @@ android {
}
kotlinOptions {
jvmTarget = "1.8"
freeCompilerArgs = freeCompilerArgs + listOf(
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api"
)
}
}

Expand Down Expand Up @@ -97,12 +100,13 @@ 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)
implementation(platform("com.google.firebase:firebase-bom:34.3.0"))
implementation("com.google.firebase:firebase-analytics")
implementation("androidx.compose.material:material-icons-extended")
}

apollo {
Expand All @@ -114,4 +118,3 @@ apollo {
}
}
}

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
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package com.cornellappdev.score.components

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
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.size
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ChevronRight
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import com.cornellappdev.score.R
import com.cornellappdev.score.model.GameCardData
import com.cornellappdev.score.theme.CornellRed
import com.cornellappdev.score.theme.GrayMedium
import com.cornellappdev.score.theme.Style

@Composable
fun ProfileGameCarousel(
title: String,
games: List<GameCardData>,
onClick: (String) -> Unit,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier.fillMaxWidth()
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = title,
style = Style.heading6
)

Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = "${games.size} Results",
style = Style.bodyNormalGray
)
Icon(
imageVector = Icons.Outlined.ChevronRight,
contentDescription = null,
tint = GrayMedium,
modifier = Modifier.size(18.dp)
)
}
}

Spacer(modifier = Modifier.height(12.dp))

LazyRow(
contentPadding = PaddingValues(horizontal = 20.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
items(games) { game ->
FeaturedGameCard(
leftTeamLogo = painterResource(R.drawable.cornell_logo),
rightTeamLogo = game.teamLogo,
team = game.team,
date = game.dateString,
isLive = game.isLive,
isPast = game.isPast,
genderIcon = painterResource(game.genderIcon),
sportIcon = painterResource(game.sportIcon),
location = game.location,
gradientColor1 = CornellRed,
gradientColor2 = game.teamColor,
leftScore = game.cornellScore?.toInt(),
rightScore = game.otherScore?.toInt(),
onClick = { onClick(game.id) },
modifier = Modifier.width(241.dp),
headerModifier = Modifier
)
}
}

Spacer(modifier = Modifier.height(24.dp))
}
}
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,
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
35 changes: 30 additions & 5 deletions app/src/main/java/com/cornellappdev/score/nav/ScoreNavHost.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ import androidx.navigation.toRoute
import com.cornellappdev.score.model.ScoreEvent
import com.cornellappdev.score.nav.root.ScoreScreens
import com.cornellappdev.score.nav.root.ScoreScreens.Home
import com.cornellappdev.score.screen.EditProfileScreen
import com.cornellappdev.score.screen.GameDetailsScreen
import com.cornellappdev.score.screen.HighlightsScreen
import com.cornellappdev.score.screen.HighlightsSearchScreen
import com.cornellappdev.score.screen.HomeScreen
import com.cornellappdev.score.screen.PastGamesScreen
import com.cornellappdev.score.screen.ProfileScreen
import com.cornellappdev.score.util.highlightsList
import com.cornellappdev.score.util.recentSearchList
import com.cornellappdev.score.util.sportList
Expand All @@ -35,9 +37,14 @@ fun ScoreNavHost(navController: NavHostController) {
) {
composable<Home> {
CompositionLocalProvider(LocalViewModelStoreOwner provides mainScreenViewModelStoreOwner) {
HomeScreen(navigateToGameDetails = {
navController.navigate(ScoreScreens.GameDetailsPage(it))
})
HomeScreen(
navigateToGameDetails = {
navController.navigate(ScoreScreens.GameDetailsPage(it))
},
navigateToProfile = {
navController.navigate(ScoreScreens.Profile)
}
)
}
}
composable<ScoreScreens.ScoresScreen> {
Expand All @@ -47,8 +54,27 @@ fun ScoreNavHost(navController: NavHostController) {
})
}
}
composable<ScoreScreens.GameDetailsPage> {
composable<ScoreScreens.Profile> {
ProfileScreen(
navigateToEditProfile = {
navController.navigate(ScoreScreens.EditProfile)
},
navigateToGameDetails = { gameId ->
navController.navigate(ScoreScreens.GameDetailsPage(gameId))
}
)
}
composable<ScoreScreens.EditProfile> {
EditProfileScreen(
onBackClick = {
navController.navigateUp()
}
)
}
Comment on lines +57 to +73
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

looks good!

composable<ScoreScreens.GameDetailsPage> { backStackEntry ->
val route = backStackEntry.toRoute<ScoreScreens.GameDetailsPage>()
GameDetailsScreen(
gameId = route.gameId,
onBackArrow = {
navController.navigateUp()
},
Expand Down Expand Up @@ -89,4 +115,3 @@ fun ScoreNavHost(navController: NavHostController) {
// }
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.NavBackStackEntry
Expand Down Expand Up @@ -38,8 +39,10 @@ fun ScoreNavigationBar(
selectedIndicatorColor = Color.Transparent
),
icon = {
val icon = if (isSelected) item.selectedIcon else item.unselectedIcon

Icon(
painter = painterResource(id = if (isSelected) item.selectedIcon else item.unselectedIcon),
painter = painterResource(id = icon),
contentDescription = null,
tint = Color.Unspecified
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.outlined.Person
import androidx.compose.material.icons.outlined.Schedule
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
Expand All @@ -18,6 +22,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.dropShadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.shadow.Shadow
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
Expand Down Expand Up @@ -65,7 +70,8 @@ fun RootNavigation(

Scaffold(
modifier = modifier.fillMaxSize(), bottomBar = {
if (navBackStackEntry?.toScreen() is ScoreScreens.GameDetailsPage) {
val currentScreen = navBackStackEntry?.toScreen()
if (currentScreen is ScoreScreens.GameDetailsPage || currentScreen is ScoreScreens.EditProfile) {
return@Scaffold
}
Surface(
Expand Down Expand Up @@ -110,6 +116,12 @@ sealed class ScoreScreens {
@Serializable
data class GameScoreSummaryPage(val scoreEvents: String) : ScoreScreens()

@Serializable
data object Profile : ScoreScreens()

@Serializable
data object EditProfile : ScoreScreens()

////removed for 2/2026 release
// @Serializable
// data object HighlightsScreen : ScoreScreens()
Expand All @@ -124,10 +136,12 @@ fun NavBackStackEntry.toScreen(): ScoreScreens? =
"GameDetailsPage" -> toRoute<ScoreScreens.GameDetailsPage>()
"ScoresScreen" -> toRoute<ScoreScreens.ScoresScreen>()
"GameScoreSummaryPage" -> toRoute<ScoreScreens.GameScoreSummaryPage>()
"Profile" -> toRoute<ScoreScreens.Profile>()
"EditProfile" -> toRoute<ScoreScreens.EditProfile>()
//removed for 2/2026 release
// "HighlightsScreen" -> toRoute<ScoreScreens.HighlightsScreen>()
// "HighlightsSearchScreen" -> toRoute<ScoreScreens.HighlightsScreen>()
else -> throw IllegalArgumentException("Invalid screen")
else -> null
}

data class NavItem(
Expand Down Expand Up @@ -157,4 +171,4 @@ val tabs = listOf(
selectedIcon = R.drawable.ic_scores_filled,
screen = ScoreScreens.ScoresScreen,
),
)
)
Loading
Loading