From 6b64bcb3d0a61409cd917826723f9a90d8e46477 Mon Sep 17 00:00:00 2001 From: amjiao Date: Sat, 8 Mar 2025 13:11:59 -0500 Subject: [PATCH 01/19] Date functions refactor --- .../com/cornellappdev/score/util/DateUtil.kt | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 app/src/main/java/com/cornellappdev/score/util/DateUtil.kt diff --git a/app/src/main/java/com/cornellappdev/score/util/DateUtil.kt b/app/src/main/java/com/cornellappdev/score/util/DateUtil.kt new file mode 100644 index 0000000..ac97dab --- /dev/null +++ b/app/src/main/java/com/cornellappdev/score/util/DateUtil.kt @@ -0,0 +1,39 @@ +package com.cornellappdev.score.util + +import java.time.LocalDate +import java.time.format.DateTimeFormatter; + +/** + * Converts date of form String "month-abbr day (day-of-week)" (for example, "Apr 29 (Tue)") to a LocalDate object + */ + +fun formatDate(strDate: String): LocalDate? { + val subDate = strDate.substringBefore(" (") + val formatter = DateTimeFormatter.ofPattern("MMM d") + + return try { + LocalDate.parse("$subDate ${LocalDate.now().year}", formatter) //assumes current year + } catch (e: Exception) { + null + } +} + +/** + * Converts from format "#xxxxxx" to a valid hex, with alpha = 40. Ready to be passed into Color() + */ +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 +} + +/** + * Takes in a LocalDate? object and returns it as a String of format "MM/DD/YYY" + */ +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}" +} From 794d74dce0e4fc66d1a43cecafc3843dce57524b Mon Sep 17 00:00:00 2001 From: amjiao Date: Sun, 9 Mar 2025 00:20:50 -0500 Subject: [PATCH 02/19] Add Result.kt --- .../com/cornellappdev/score/model/Result.kt | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 app/src/main/java/com/cornellappdev/score/model/Result.kt diff --git a/app/src/main/java/com/cornellappdev/score/model/Result.kt b/app/src/main/java/com/cornellappdev/score/model/Result.kt new file mode 100644 index 0000000..71c065a --- /dev/null +++ b/app/src/main/java/com/cornellappdev/score/model/Result.kt @@ -0,0 +1,24 @@ +package com.cornellappdev.score.model + +import com.apollographql.apollo.api.ApolloResponse +import com.apollographql.apollo.api.Operation +import com.apollographql.apollo.exception.NoDataException + +/** + * Maps an ApolloResponse to a generic Kotlin result. It either provides the data with no errors, or + * a failure response containing the error message in the throwable. + */ +fun ApolloResponse.toResult(): Result { + if (hasErrors() || exception != null) { + return Result.failure( + exception?.cause ?: RuntimeException( + errors?.firstOrNull()?.message ?: "Unknown error occurred" + ) + ) + } + return try { + Result.success(dataOrThrow()) + } catch (e: NoDataException) { + Result.failure(e) + } +} \ No newline at end of file From d131253db9c0259b8a9a45abba767379c08b51a1 Mon Sep 17 00:00:00 2001 From: amjiao Date: Sun, 9 Mar 2025 00:21:12 -0500 Subject: [PATCH 03/19] functioning refactored HomeViewModel --- .../cornellappdev/score/screen/HomeScreen.kt | 151 ++++++++---- .../score/viewmodel/HomeViewModel.kt | 215 ++++++++---------- 2 files changed, 203 insertions(+), 163 deletions(-) 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 27194d0..899b314 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/HomeScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/HomeScreen.kt @@ -1,21 +1,26 @@ package com.cornellappdev.score.screen +import android.content.res.Configuration import android.os.Build import androidx.annotation.RequiresApi import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.lazy.LazyColumn +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 import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -23,70 +28,128 @@ import androidx.hilt.navigation.compose.hiltViewModel import com.cornellappdev.score.components.SportCard import com.cornellappdev.score.components.SportSelectorHeader import com.cornellappdev.score.components.UpcomingGamesCarousel +import com.cornellappdev.score.model.ApiResponse import com.cornellappdev.score.theme.Style.title import com.cornellappdev.score.viewmodel.HomeViewModel -@RequiresApi(Build.VERSION_CODES.O)//TODO - change the manifest or leave this? @Composable fun HomeScreen( homeViewModel: HomeViewModel = hiltViewModel() ) { - val uiState by homeViewModel.uiStateFlow.collectAsState() + val uiState = homeViewModel.collectUiStateValue() + val filteredGames = uiState.filteredGames Column( verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top), modifier = Modifier.statusBarsPadding() - ) - { - //TODO: check - displaying the earliest three games - UpcomingGamesCarousel( - uiState.upcomingGameList.subList( - 0, - minOf(3, uiState.upcomingGameList.size) - ) - ) - Column { - Text( - text = "Game Schedule", - style = title, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 24.dp, vertical = 8.dp) - ) - Spacer(modifier = Modifier.height(8.dp)) - SportSelectorHeader( - sports = uiState.selectionList, - selectedGender = uiState.selectedGender, - selectedSport = uiState.sportSelect, - onGenderSelected = { - homeViewModel.onGenderSelected(it) - }, - onSportSelected = { - homeViewModel.onSportSelected(it) + ) { + when (uiState.loadedState) { + is ApiResponse.Loading -> { + // Show a loading indicator + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() } - ) - LazyColumn(modifier = Modifier.padding(horizontal = 24.dp)) { - items(uiState.filteredGames.size) { page -> - val game = uiState.filteredGames[page] - SportCard( - teamLogo = game.teamLogo,//painterResource(game.teamLogo), - team = game.team, - date = game.dateString, - isLive = game.isLive, - genderIcon = painterResource(game.genderIcon), - sportIcon = painterResource(game.sportIcon), - location = game.location, - topCornerRound = true + } + is ApiResponse.Error -> { + // Show an error message + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Text( + text = "Failed to load games. Please try again.", + ) + } + } + is ApiResponse.Success -> { + UpcomingGamesCarousel(filteredGames) + Column { + Text( + text = "Game Schedule", + style = title, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp, vertical = 8.dp) + ) + Spacer(modifier = Modifier.height(8.dp)) + SportSelectorHeader( + sports = uiState.selectionList, + selectedGender = uiState.selectedGender, + selectedSport = uiState.sportSelect, + onGenderSelected = { homeViewModel.onGenderSelected(it) }, + onSportSelected = { homeViewModel.onSportSelected(it) } ) + LazyColumn(modifier = Modifier.padding(horizontal = 24.dp)) { + items(filteredGames.size) { page -> + val game = filteredGames[page] + SportCard( + 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 + ) + } + } } } } } +// Column( +// verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top), +// modifier = Modifier.statusBarsPadding() +// ) +// { +// UpcomingGamesCarousel( +// uiState.filteredGames +// ) +// Column { +// Text( +// text = "Game Schedule", +// style = title, +// modifier = Modifier +// .fillMaxWidth() +// .padding(horizontal = 24.dp, vertical = 8.dp) +// ) +// Spacer(modifier = Modifier.height(8.dp)) +// SportSelectorHeader( +// sports = uiState.selectionList, +// selectedGender = uiState.selectedGender, +// selectedSport = uiState.sportSelect, +// onGenderSelected = { +// homeViewModel.onGenderSelected(it) +// }, +// onSportSelected = { +// homeViewModel.onSportSelected(it) +// } +// ) +// LazyColumn(modifier = Modifier.padding(horizontal = 24.dp)) { +// items(uiState.filteredGames.size) { page -> +// val game = uiState.filteredGames[page] +// SportCard( +// 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 +// ) +// } +// } +// } +// } } -@RequiresApi(Build.VERSION_CODES.O) @Preview(showBackground = true) -@Preview(showBackground = true, uiMode = android.content.res.Configuration.UI_MODE_NIGHT_YES) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable fun HomeScreenPreview() { HomeScreen() diff --git a/app/src/main/java/com/cornellappdev/score/viewmodel/HomeViewModel.kt b/app/src/main/java/com/cornellappdev/score/viewmodel/HomeViewModel.kt index 5b9c5c7..1c3cee6 100644 --- a/app/src/main/java/com/cornellappdev/score/viewmodel/HomeViewModel.kt +++ b/app/src/main/java/com/cornellappdev/score/viewmodel/HomeViewModel.kt @@ -1,22 +1,17 @@ package com.cornellappdev.score.viewmodel -import android.os.Build import android.util.Log -import androidx.annotation.RequiresApi -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.GenderDivision import com.cornellappdev.score.model.ScoreRepository import com.cornellappdev.score.model.Sport import com.cornellappdev.score.model.SportSelection -import com.cornellappdev.score.nav.root.RootNavigationRepository +import com.cornellappdev.score.util.dateToString +import com.cornellappdev.score.util.formatColor +import com.cornellappdev.score.util.formatDate 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 @@ -24,28 +19,112 @@ data class HomeUiState( val selectedGender: GenderDivision, val sportSelect: SportSelection, val selectionList: List, - val upcomingGameList: List, - // TODO Add remaining dynamic data for UI + //private val upcomingGameList: List, + val loadedState: ApiResponse> ) { val filteredGames: List - get() = upcomingGameList.filter { game -> - (selectedGender == GenderDivision.ALL || game.gender == selectedGender.displayName) - && (sportSelect is SportSelection.All || (sportSelect is SportSelection.SportSelect && game.sport == sportSelect.sport.displayName)) + get() = when (loadedState) { + is ApiResponse.Success -> loadedState.data.filter { game -> + (selectedGender == GenderDivision.ALL || game.gender == selectedGender.displayName) && + (sportSelect is SportSelection.All || (sportSelect is SportSelection.SportSelect && game.sport == sportSelect.sport.displayName)) } + ApiResponse.Loading -> emptyList() + ApiResponse.Error -> emptyList() + } } @HiltViewModel class HomeViewModel @Inject constructor( - private val rootNavigationRepository: RootNavigationRepository, + //private val rootNavigationViewModel: RootNavigationViewModel, private val scoreRepository: ScoreRepository ) : BaseViewModel( - initialUiState = HomeUiState( + HomeUiState( selectedGender = GenderDivision.ALL, sportSelect = SportSelection.All, selectionList = Sport.getSportSelectionList(), - upcomingGameList = emptyList() + loadedState = ApiResponse.Loading ) ) { + init { + Log.d("HomeViewModel", "init reached") + scoreRepository.fetchGames() + Log.d("HomeViewModel", "games fetched") + + asyncCollect(scoreRepository.upcomingGamesFlow) { response -> + applyMutation { + copy( + loadedState = when (response) { + is ApiResponse.Success -> { + val gameCards = response.data + .map { game -> + GameCardData( + teamLogo = game.teamLogo, + team = game.teamName, + teamColor = formatColor(game.teamColor), + date = formatDate(game.date), + dateString = dateToString(formatDate(game.date)), + isLive = (LocalDate.now() == formatDate(game.date)), + location = game.city, + gender = game.gender, + genderIcon = if (game.gender == "Mens") R.drawable.ic_gender_men else R.drawable.ic_gender_women, + sport = game.sport, + sportIcon = Sport.fromDisplayName(game.sport)?.emptyIcon + ?: R.drawable.ic_empty_placeholder + ) + } + .sortedBy { it.date } + ApiResponse.Success(gameCards) + } + + ApiResponse.Loading -> ApiResponse.Loading + ApiResponse.Error -> ApiResponse.Error + } + ) + } + } + } +// asyncCollect(scoreRepository.upcomingGamesFlow) { response -> +// Log.d("HomeViewModel", "asyncCollect triggered with response: $response") +// applyMutation { +// Log.d("HomeViewModel", "applyMutation called with data: ${response is ApiResponse.Success}") +// copy( +// loadedState = when (response) { +// is ApiResponse.Success -> { +// val gameCards = response.data.map { game -> +// GameCardData( +// teamLogo = game.teamLogo, +// team = game.teamName, +// teamColor = formatColor(game.teamColor), +// date = formatDate(game.date), +// dateString = dateToString(formatDate(game.date)), +// isLive = (LocalDate.now() == formatDate(game.date)), +// location = game.city, +// gender = game.gender, +// genderIcon = if (game.gender == "Mens") R.drawable.ic_gender_men else R.drawable.ic_gender_women, +// sport = game.sport, +// sportIcon = Sport.fromDisplayName(game.sport)?.emptyIcon +// ?: R.drawable.ic_empty_placeholder +// ) +// }.sortedBy { it.date } +// Log.d("HomeViewModel", "Processed ${gameCards.size} games") +// ApiResponse.Success(gameCards) +// } +// +// ApiResponse.Loading -> { +// Log.d("HomeViewModel", "Data is still loading") +// ApiResponse.Loading +// } +// +// ApiResponse.Error -> { +// Log.e("HomeViewModel", "Error in data fetching") +// ApiResponse.Error +// } +// } +// ) +// } +// } +// } + fun onGenderSelected(gender: GenderDivision) { applyMutation { copy( @@ -61,106 +140,4 @@ class HomeViewModel @Inject constructor( ) } } - - private fun updateGameList(response: ApiResponse>) { - val games: List = when (response) { - is ApiResponse.Success -> response.data - ApiResponse.Error -> emptyList() - ApiResponse.Loading -> emptyList() - } - - val gameCards = games.filter { game -> - val currentDate = LocalDate.now() - val tomorrowDate = LocalDate.now().plusDays(1) - val formattedDate = formatDate(game.date) - formattedDate == currentDate || formattedDate == tomorrowDate - }.map { game -> - GameCardData( - teamLogo = game.teamLogo, - team = game.teamName, - teamColor = formatColor(game.teamColor), - date = formatDate(game.date), - dateString = dateToString(formatDate(game.date)), - isLive = (LocalDate.now() == formatDate(game.date)), - location = game.city, - gender = game.gender, - genderIcon = if (game.gender == "Mens") R.drawable.ic_gender_men else R.drawable.ic_gender_women, - sport = game.sport, - sportIcon = Sport.fromDisplayName(game.sport)?.emptyIcon - ?: R.drawable.ic_empty_placeholder - ) - }.sortedBy { it.date } - - applyMutation { - copy(upcomingGameList = gameCards) - } - } - -// fun onRefresh() { -// viewModelScope.launch { -// val response = scoreRepository.fetchGames() -// updateGameList(response) -// } -// } - - //Converts date from String "month day" to a LocalDate object - 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}" - } - - private fun observeUpcomingGames() = scoreRepository.upcomingGamesFlow.onEach { response -> - //Log.d("HomeViewModel", "Response: $response") - updateGameList(response) - }.launchIn(viewModelScope) - - init { - observeUpcomingGames() - viewModelScope.launch{ - scoreRepository.fetchGames() - } - } -} \ No newline at end of file +} From f7471046d1a30fe64b9112f457f78540315d1483 Mon Sep 17 00:00:00 2001 From: amjiao Date: Sun, 9 Mar 2025 00:24:36 -0500 Subject: [PATCH 04/19] Reformat code --- .../score/components/SportCard.kt | 16 +++-- .../cornellappdev/score/screen/HomeScreen.kt | 56 ++--------------- .../score/viewmodel/HomeViewModel.kt | 60 ++++--------------- 3 files changed, 25 insertions(+), 107 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/score/components/SportCard.kt b/app/src/main/java/com/cornellappdev/score/components/SportCard.kt index 42a935d..cf78ecf 100644 --- a/app/src/main/java/com/cornellappdev/score/components/SportCard.kt +++ b/app/src/main/java/com/cornellappdev/score/components/SportCard.kt @@ -1,6 +1,5 @@ package com.cornellappdev.score.components -import android.icu.text.SimpleDateFormat import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.infiniteRepeatable @@ -9,7 +8,14 @@ import androidx.compose.animation.core.tween import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +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.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Card @@ -36,8 +42,6 @@ import com.cornellappdev.score.theme.Style.dateText import com.cornellappdev.score.theme.Style.teamName import com.cornellappdev.score.theme.Style.universityText import com.cornellappdev.score.theme.saturatedGreen -import java.util.Date -import java.util.Locale @Composable fun SportCard( @@ -97,7 +101,9 @@ fun SportCard( ) { AsyncImage( model = teamLogo, - modifier = Modifier.height(20.dp).padding(start = 4.dp, end = 4.dp), + modifier = Modifier + .height(20.dp) + .padding(start = 4.dp, end = 4.dp), contentDescription = "" ) 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 899b314..0697aea 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/HomeScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/HomeScreen.kt @@ -1,8 +1,6 @@ package com.cornellappdev.score.screen import android.content.res.Configuration -import android.os.Build -import androidx.annotation.RequiresApi import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -16,11 +14,8 @@ import androidx.compose.foundation.lazy.LazyColumn 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 import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -45,7 +40,7 @@ fun HomeScreen( ) { when (uiState.loadedState) { is ApiResponse.Loading -> { - // Show a loading indicator + //TODO: Add loading screen Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center @@ -53,8 +48,9 @@ fun HomeScreen( CircularProgressIndicator() } } + is ApiResponse.Error -> { - // Show an error message + //TODO: Add Error screen Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center @@ -64,6 +60,7 @@ fun HomeScreen( ) } } + is ApiResponse.Success -> { UpcomingGamesCarousel(filteredGames) Column { @@ -101,51 +98,6 @@ fun HomeScreen( } } } -// Column( -// verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top), -// modifier = Modifier.statusBarsPadding() -// ) -// { -// UpcomingGamesCarousel( -// uiState.filteredGames -// ) -// Column { -// Text( -// text = "Game Schedule", -// style = title, -// modifier = Modifier -// .fillMaxWidth() -// .padding(horizontal = 24.dp, vertical = 8.dp) -// ) -// Spacer(modifier = Modifier.height(8.dp)) -// SportSelectorHeader( -// sports = uiState.selectionList, -// selectedGender = uiState.selectedGender, -// selectedSport = uiState.sportSelect, -// onGenderSelected = { -// homeViewModel.onGenderSelected(it) -// }, -// onSportSelected = { -// homeViewModel.onSportSelected(it) -// } -// ) -// LazyColumn(modifier = Modifier.padding(horizontal = 24.dp)) { -// items(uiState.filteredGames.size) { page -> -// val game = uiState.filteredGames[page] -// SportCard( -// 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 -// ) -// } -// } -// } -// } } @Preview(showBackground = true) diff --git a/app/src/main/java/com/cornellappdev/score/viewmodel/HomeViewModel.kt b/app/src/main/java/com/cornellappdev/score/viewmodel/HomeViewModel.kt index 1c3cee6..a818873 100644 --- a/app/src/main/java/com/cornellappdev/score/viewmodel/HomeViewModel.kt +++ b/app/src/main/java/com/cornellappdev/score/viewmodel/HomeViewModel.kt @@ -8,6 +8,7 @@ import com.cornellappdev.score.model.GenderDivision import com.cornellappdev.score.model.ScoreRepository import com.cornellappdev.score.model.Sport import com.cornellappdev.score.model.SportSelection +import com.cornellappdev.score.nav.root.RootNavigationViewModel import com.cornellappdev.score.util.dateToString import com.cornellappdev.score.util.formatColor import com.cornellappdev.score.util.formatDate @@ -19,23 +20,23 @@ data class HomeUiState( val selectedGender: GenderDivision, val sportSelect: SportSelection, val selectionList: List, - //private val upcomingGameList: List, val loadedState: ApiResponse> ) { val filteredGames: List - get() = when (loadedState) { - is ApiResponse.Success -> loadedState.data.filter { game -> - (selectedGender == GenderDivision.ALL || game.gender == selectedGender.displayName) && - (sportSelect is SportSelection.All || (sportSelect is SportSelection.SportSelect && game.sport == sportSelect.sport.displayName)) + get() = when (loadedState) { + is ApiResponse.Success -> loadedState.data.filter { game -> + (selectedGender == GenderDivision.ALL || game.gender == selectedGender.displayName) && + (sportSelect is SportSelection.All || (sportSelect is SportSelection.SportSelect && game.sport == sportSelect.sport.displayName)) + } + + ApiResponse.Loading -> emptyList() + ApiResponse.Error -> emptyList() } - ApiResponse.Loading -> emptyList() - ApiResponse.Error -> emptyList() - } } @HiltViewModel class HomeViewModel @Inject constructor( - //private val rootNavigationViewModel: RootNavigationViewModel, + private val rootNavigationViewModel: RootNavigationViewModel, private val scoreRepository: ScoreRepository ) : BaseViewModel( HomeUiState( @@ -83,47 +84,6 @@ class HomeViewModel @Inject constructor( } } } -// asyncCollect(scoreRepository.upcomingGamesFlow) { response -> -// Log.d("HomeViewModel", "asyncCollect triggered with response: $response") -// applyMutation { -// Log.d("HomeViewModel", "applyMutation called with data: ${response is ApiResponse.Success}") -// copy( -// loadedState = when (response) { -// is ApiResponse.Success -> { -// val gameCards = response.data.map { game -> -// GameCardData( -// teamLogo = game.teamLogo, -// team = game.teamName, -// teamColor = formatColor(game.teamColor), -// date = formatDate(game.date), -// dateString = dateToString(formatDate(game.date)), -// isLive = (LocalDate.now() == formatDate(game.date)), -// location = game.city, -// gender = game.gender, -// genderIcon = if (game.gender == "Mens") R.drawable.ic_gender_men else R.drawable.ic_gender_women, -// sport = game.sport, -// sportIcon = Sport.fromDisplayName(game.sport)?.emptyIcon -// ?: R.drawable.ic_empty_placeholder -// ) -// }.sortedBy { it.date } -// Log.d("HomeViewModel", "Processed ${gameCards.size} games") -// ApiResponse.Success(gameCards) -// } -// -// ApiResponse.Loading -> { -// Log.d("HomeViewModel", "Data is still loading") -// ApiResponse.Loading -// } -// -// ApiResponse.Error -> { -// Log.e("HomeViewModel", "Error in data fetching") -// ApiResponse.Error -// } -// } -// ) -// } -// } -// } fun onGenderSelected(gender: GenderDivision) { applyMutation { From 87bd8ac9e74e85a42a0154969709d1f88de24821 Mon Sep 17 00:00:00 2001 From: amjiao Date: Sun, 9 Mar 2025 00:24:50 -0500 Subject: [PATCH 05/19] Gradle + Library updates --- app/build.gradle.kts | 1 + gradle/libs.versions.toml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b2b47b9..e963060 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -63,6 +63,7 @@ dependencies { implementation("androidx.navigation:navigation-compose:2.8.2") implementation("androidx.compose.material3:material3:1.0.0") implementation("com.google.dagger:hilt-android:2.51.1") + implementation(libs.androidx.core.i18n) kapt("com.google.dagger:hilt-android-compiler:2.51.1") implementation("androidx.hilt:hilt-navigation-compose:1.0.0") implementation("com.google.accompanist:accompanist-pager:0.24.0-alpha") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 53e3f98..12d874e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,6 +11,7 @@ activity = "1.8.0" constraintlayout = "2.1.4" runtimeAndroid = "1.7.2" apollo = "4.1.1" +coreI18n = "1.0.0-alpha01" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -23,6 +24,7 @@ androidx-activity = { group = "androidx.activity", name = "activity", version.re androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } androidx-runtime-android = { group = "androidx.compose.runtime", name = "runtime-android", version.ref = "runtimeAndroid" } apollo-runtime = { module = "com.apollographql.apollo:apollo-runtime" } +androidx-core-i18n = { group = "androidx.core", name = "core-i18n", version.ref = "coreI18n" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } From 6759922a2a0515ed0160fc6c2b91f9316226f1b6 Mon Sep 17 00:00:00 2001 From: amjiao Date: Sun, 9 Mar 2025 00:36:44 -0500 Subject: [PATCH 06/19] Having RootNavigation Hilt/injection issues? Commented them out since they currently aren't being used --- .../java/com/cornellappdev/score/nav/root/RootNavigation.kt | 3 --- .../java/com/cornellappdev/score/viewmodel/HomeViewModel.kt | 2 +- 2 files changed, 1 insertion(+), 4 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 e6db353..440ad57 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 @@ -1,7 +1,5 @@ package com.cornellappdev.score.nav.root -import android.os.Build -import androidx.annotation.RequiresApi import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.hilt.navigation.compose.hiltViewModel @@ -11,7 +9,6 @@ import androidx.navigation.compose.rememberNavController import com.cornellappdev.score.screen.HomeScreen import kotlinx.serialization.Serializable -@RequiresApi(Build.VERSION_CODES.O) @Composable fun RootNavigation( rootNavigationViewModel: RootNavigationViewModel = hiltViewModel(), diff --git a/app/src/main/java/com/cornellappdev/score/viewmodel/HomeViewModel.kt b/app/src/main/java/com/cornellappdev/score/viewmodel/HomeViewModel.kt index a818873..f90d87a 100644 --- a/app/src/main/java/com/cornellappdev/score/viewmodel/HomeViewModel.kt +++ b/app/src/main/java/com/cornellappdev/score/viewmodel/HomeViewModel.kt @@ -36,7 +36,7 @@ data class HomeUiState( @HiltViewModel class HomeViewModel @Inject constructor( - private val rootNavigationViewModel: RootNavigationViewModel, + //private val rootNavigationViewModel: RootNavigationViewModel, private val scoreRepository: ScoreRepository ) : BaseViewModel( HomeUiState( From daff2de96faf2d378b08eccd978cc40aa0e55948 Mon Sep 17 00:00:00 2001 From: amjiao Date: Sun, 9 Mar 2025 00:37:10 -0500 Subject: [PATCH 07/19] Added Result checks to ScoreRepository --- .../score/model/ScoreRepository.kt | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 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 073c0e4..7e7627d 100644 --- a/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt +++ b/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt @@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import javax.inject.Inject import javax.inject.Singleton +import com.cornellappdev.score.model.toResult /** * This is a singleton responsible for fetching and caching all data for Score. @@ -35,24 +36,31 @@ class ScoreRepository @Inject constructor( _upcomingGamesFlow.value = ApiResponse.Loading try { val response = (apolloClient.query(GamesQuery()).execute()) - val games = response.data?.games ?: emptyList() - Log.d("ScoreRepository", "response fetched successfully") + val result = response.toResult() - val list: List = games.mapNotNull { game -> - game?.team?.image?.let { - Game( - teamLogo = it,//game.team.image, - teamName = game.team.name, - teamColor = game.team.color, - gender = game.gender, - sport = game.sport, - date = game.date, - city = game.city - ) + if(result.isSuccess){ + val games = response.data?.games ?: emptyList() + Log.d("ScoreRepository", "response fetched successfully") + + val list: List = games.mapNotNull { game -> + game?.team?.image?.let { + Game( + teamLogo = it,//game.team.image, + teamName = game.team.name, + teamColor = game.team.color, + gender = game.gender, + sport = game.sport, + date = game.date, + city = game.city + ) + } } + Log.d("ScoreRepository", "#games: ${list.size}") + _upcomingGamesFlow.value = ApiResponse.Success(list.toList()) + }else{ + _upcomingGamesFlow.value = ApiResponse.Error } - Log.d("ScoreRepository", "#games: ${list.size}") - _upcomingGamesFlow.value = ApiResponse.Success(list.toList()) + } catch (e: Exception) { Log.e("ScoreRepository", "Error fetching posts: ", e) _upcomingGamesFlow.value = ApiResponse.Error From e01764db6fc52feca13bc83beaa50f234be05169 Mon Sep 17 00:00:00 2001 From: amjiao Date: Sun, 9 Mar 2025 00:46:28 -0500 Subject: [PATCH 08/19] Updated to display all games in LazyColumn and first 3 games in upcomingGamesHeader --- .../main/java/com/cornellappdev/score/screen/HomeScreen.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 0697aea..1f694a6 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/HomeScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/HomeScreen.kt @@ -26,6 +26,7 @@ import com.cornellappdev.score.components.UpcomingGamesCarousel import com.cornellappdev.score.model.ApiResponse import com.cornellappdev.score.theme.Style.title import com.cornellappdev.score.viewmodel.HomeViewModel +import kotlin.math.min @Composable fun HomeScreen( @@ -33,6 +34,7 @@ fun HomeScreen( ) { val uiState = homeViewModel.collectUiStateValue() val filteredGames = uiState.filteredGames + val firstThreeGames = filteredGames.subList(0, min(filteredGames.size, 3)) //TODO: is this okay to be in the view? Column( verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top), @@ -62,7 +64,7 @@ fun HomeScreen( } is ApiResponse.Success -> { - UpcomingGamesCarousel(filteredGames) + UpcomingGamesCarousel(firstThreeGames) Column { Text( text = "Game Schedule", From 66fbd8093e2c90d0a59e729fb0fd72abfc4b9f2a Mon Sep 17 00:00:00 2001 From: amjiao Date: Sun, 9 Mar 2025 16:23:41 -0400 Subject: [PATCH 09/19] small fixes --- .../cornellappdev/score/screen/HomeScreen.kt | 145 ++++++++++++++---- gradle/libs.versions.toml | 2 +- 2 files changed, 113 insertions(+), 34 deletions(-) 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 54ffd34..f442c48 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/HomeScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/HomeScreen.kt @@ -24,7 +24,6 @@ import com.cornellappdev.score.components.SportCard import com.cornellappdev.score.components.SportSelectorHeader import com.cornellappdev.score.components.UpcomingGamesCarousel import com.cornellappdev.score.model.ApiResponse -import com.cornellappdev.score.theme.Style.heading1 import com.cornellappdev.score.theme.Style.title import com.cornellappdev.score.viewmodel.HomeViewModel import kotlin.math.min @@ -50,37 +49,7 @@ fun HomeScreen( ) { CircularProgressIndicator() } - - //TODO: check - displaying the earliest three games - UpcomingGamesCarousel( - uiState.upcomingGameList.subList( - 0, - minOf(3, uiState.upcomingGameList.size) - ) - ) - Column { - Text( - text = "Game Schedule", - style = heading1, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 24.dp, vertical = 8.dp) - ) - Spacer(modifier = Modifier.height(8.dp)) - SportSelectorHeader( - sports = uiState.selectionList, - selectedGender = uiState.selectedGender, - selectedSport = uiState.sportSelect, - onGenderSelected = { - homeViewModel.onGenderSelected(it) - }, - onSportSelected = { - homeViewModel.onSportSelected(it) - } - ) - } } - is ApiResponse.Error -> { //TODO: Add Error screen Box( @@ -132,9 +101,119 @@ fun HomeScreen( } } -@Preview(showBackground = true) -@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Preview @Composable private fun HomeScreenPreview() { HomeScreen() } + + +//@Composable +//fun HomeScreen( +// homeViewModel: HomeViewModel = hiltViewModel() +//) { +// val uiState = homeViewModel.collectUiStateValue() +// val filteredGames = uiState.filteredGames +// val firstThreeGames = filteredGames.subList(0, min(filteredGames.size, 3)) //TODO: is this okay to be in the view? +// +// Column( +// verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top), +// modifier = Modifier.statusBarsPadding() +// ) { +// when (uiState.loadedState) { +// is ApiResponse.Loading -> { +// //TODO: Add loading screen +// Box( +// modifier = Modifier.fillMaxSize(), +// contentAlignment = Alignment.Center +// ) { +// CircularProgressIndicator() +// } +// +// //TODO: check - displaying the earliest three games +// UpcomingGamesCarousel( +// uiState.upcomingGameList.subList( +// 0, +// minOf(3, uiState.upcomingGameList.size) +// ) +// ) +// Column { +// Text( +// text = "Game Schedule", +// style = heading1, +// modifier = Modifier +// .fillMaxWidth() +// .padding(horizontal = 24.dp, vertical = 8.dp) +// ) +// Spacer(modifier = Modifier.height(8.dp)) +// SportSelectorHeader( +// sports = uiState.selectionList, +// selectedGender = uiState.selectedGender, +// selectedSport = uiState.sportSelect, +// onGenderSelected = { +// homeViewModel.onGenderSelected(it) +// }, +// onSportSelected = { +// homeViewModel.onSportSelected(it) +// } +// ) +// } +// } +// +// is ApiResponse.Error -> { +// //TODO: Add Error screen +// Box( +// modifier = Modifier.fillMaxSize(), +// contentAlignment = Alignment.Center +// ) { +// Text( +// text = "Failed to load games. Please try again.", +// ) +// } +// } +// +// is ApiResponse.Success -> { +// UpcomingGamesCarousel(firstThreeGames) +// Column { +// Text( +// text = "Game Schedule", +// style = title, +// modifier = Modifier +// .fillMaxWidth() +// .padding(horizontal = 24.dp, vertical = 8.dp) +// ) +// Spacer(modifier = Modifier.height(8.dp)) +// SportSelectorHeader( +// sports = uiState.selectionList, +// selectedGender = uiState.selectedGender, +// selectedSport = uiState.sportSelect, +// onGenderSelected = { homeViewModel.onGenderSelected(it) }, +// onSportSelected = { homeViewModel.onSportSelected(it) } +// ) +// LazyColumn(modifier = Modifier.padding(horizontal = 24.dp)) { +// items(filteredGames.size) { page -> +// val game = filteredGames[page] +// SportCard( +// 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 +// ) +// } +// } +// } +// } +// } +// } +//} +// +//@Preview(showBackground = true) +//@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +//@Composable +//private fun HomeScreenPreview() { +// HomeScreen() +//} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 179d961..7124742 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.8.2" +agp = "8.7.3" composeLintChecks = "1.4.2" kotlin = "1.9.0" coreKtx = "1.10.1" From a46ddfb7bd9ef1b713be02ee5f54c2af21759727 Mon Sep 17 00:00:00 2001 From: amjiao Date: Mon, 17 Mar 2025 00:44:08 -0400 Subject: [PATCH 10/19] PR fixes checkpoint --- app/build.gradle.kts | 1 - .../com/cornellappdev/score/model/Game.kt | 2 +- .../score/model/ScoreRepository.kt | 36 ++-- .../cornellappdev/score/screen/HomeScreen.kt | 202 +++++------------- .../com/cornellappdev/score/util/DateUtil.kt | 5 +- .../score/viewmodel/HomeViewModel.kt | 15 +- gradle/libs.versions.toml | 2 - 7 files changed, 79 insertions(+), 184 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index fd1cb13..5642ff1 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -71,7 +71,6 @@ dependencies { implementation("androidx.navigation:navigation-compose:2.8.2") implementation("androidx.compose.material3:material3:1.0.0") implementation("com.google.dagger:hilt-android:2.51.1") - implementation(libs.androidx.core.i18n) kapt("com.google.dagger:hilt-android-compiler:2.51.1") implementation("androidx.hilt:hilt-navigation-compose:1.0.0") implementation("com.google.accompanist:accompanist-pager:0.24.0-alpha") 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..eb939d0 100644 --- a/app/src/main/java/com/cornellappdev/score/model/Game.kt +++ b/app/src/main/java/com/cornellappdev/score/model/Game.kt @@ -8,7 +8,7 @@ import java.time.LocalDate data class Game( val teamName: String, val teamLogo: String, - val teamColor: String, + val teamColor: String, //should this also be a Color? val gender: String, val sport: String, val date: 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 7e7627d..d33fbc0 100644 --- a/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt +++ b/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt @@ -35,28 +35,26 @@ class ScoreRepository @Inject constructor( fun fetchGames() = appScope.launch { _upcomingGamesFlow.value = ApiResponse.Loading try { - val response = (apolloClient.query(GamesQuery()).execute()) - val result = response.toResult() + val result = (apolloClient.query(GamesQuery()).execute()).toResult() if(result.isSuccess){ - val games = response.data?.games ?: emptyList() - Log.d("ScoreRepository", "response fetched successfully") + val games = result.getOrNull() - val list: List = games.mapNotNull { game -> - game?.team?.image?.let { - Game( - teamLogo = it,//game.team.image, - teamName = game.team.name, - teamColor = game.team.color, - gender = game.gender, - sport = game.sport, - date = game.date, - city = game.city - ) - } - } - Log.d("ScoreRepository", "#games: ${list.size}") - _upcomingGamesFlow.value = ApiResponse.Success(list.toList()) + val upcomingGameslist: List = + games?.games?.mapNotNull { game -> + game?.team?.image?.let { + Game( + teamLogo = it, + teamName = game.team.name, + teamColor = game.team.color, + gender = game.gender, + sport = game.sport, + date = game.date, + city = game.city + ) + } + } ?: emptyList() + _upcomingGamesFlow.value = ApiResponse.Success(upcomingGameslist) }else{ _upcomingGamesFlow.value = ApiResponse.Error } 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 f442c48..25cd0e2 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/HomeScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/HomeScreen.kt @@ -1,6 +1,5 @@ package com.cornellappdev.score.screen -import android.content.res.Configuration import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -11,6 +10,7 @@ import androidx.compose.foundation.layout.height 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.CircularProgressIndicator import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -24,17 +24,18 @@ import com.cornellappdev.score.components.SportCard import com.cornellappdev.score.components.SportSelectorHeader import com.cornellappdev.score.components.UpcomingGamesCarousel import com.cornellappdev.score.model.ApiResponse +import com.cornellappdev.score.model.GameCardData +import com.cornellappdev.score.model.GenderDivision +import com.cornellappdev.score.model.SportSelection import com.cornellappdev.score.theme.Style.title +import com.cornellappdev.score.viewmodel.HomeUiState import com.cornellappdev.score.viewmodel.HomeViewModel -import kotlin.math.min @Composable fun HomeScreen( homeViewModel: HomeViewModel = hiltViewModel() ) { val uiState = homeViewModel.collectUiStateValue() - val filteredGames = uiState.filteredGames - val firstThreeGames = filteredGames.subList(0, min(filteredGames.size, 3)) //TODO: is this okay to be in the view? Column( verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top), @@ -63,39 +64,52 @@ fun HomeScreen( } is ApiResponse.Success -> { - UpcomingGamesCarousel(firstThreeGames) - Column { - Text( - text = "Game Schedule", - style = title, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 24.dp, vertical = 8.dp) - ) - Spacer(modifier = Modifier.height(8.dp)) - SportSelectorHeader( - sports = uiState.selectionList, - selectedGender = uiState.selectedGender, - selectedSport = uiState.sportSelect, - onGenderSelected = { homeViewModel.onGenderSelected(it) }, - onSportSelected = { homeViewModel.onSportSelected(it) } - ) - LazyColumn(modifier = Modifier.padding(horizontal = 24.dp)) { - items(filteredGames.size) { page -> - val game = filteredGames[page] - SportCard( - 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 - ) - } - } - } + HomeContent( + uiState = uiState, + onGenderSelected = { homeViewModel.onGenderSelected(it) }, + onSportSelected = { homeViewModel.onSportSelected(it) } + ) + } + } + } +} + +@Composable +private fun HomeContent( + uiState: HomeUiState, + onGenderSelected: (GenderDivision) -> Unit, + onSportSelected: (SportSelection) -> Unit + ){ + UpcomingGamesCarousel(uiState.upcomingGames) + Column { + Text( + text = "Game Schedule", + style = title, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp, vertical = 8.dp) + ) + Spacer(modifier = Modifier.height(8.dp)) + SportSelectorHeader( + sports = uiState.selectionList, + selectedGender = uiState.selectedGender, + selectedSport = uiState.sportSelect, + onGenderSelected = onGenderSelected, + onSportSelected = onSportSelected + ) + LazyColumn(modifier = Modifier.padding(horizontal = 24.dp)) { + items(uiState.filteredGames){ + val game = it + SportCard( + 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 + ) } } } @@ -104,116 +118,6 @@ fun HomeScreen( @Preview @Composable private fun HomeScreenPreview() { - HomeScreen() + //TODO: parameters?? + //HomeContent() } - - -//@Composable -//fun HomeScreen( -// homeViewModel: HomeViewModel = hiltViewModel() -//) { -// val uiState = homeViewModel.collectUiStateValue() -// val filteredGames = uiState.filteredGames -// val firstThreeGames = filteredGames.subList(0, min(filteredGames.size, 3)) //TODO: is this okay to be in the view? -// -// Column( -// verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top), -// modifier = Modifier.statusBarsPadding() -// ) { -// when (uiState.loadedState) { -// is ApiResponse.Loading -> { -// //TODO: Add loading screen -// Box( -// modifier = Modifier.fillMaxSize(), -// contentAlignment = Alignment.Center -// ) { -// CircularProgressIndicator() -// } -// -// //TODO: check - displaying the earliest three games -// UpcomingGamesCarousel( -// uiState.upcomingGameList.subList( -// 0, -// minOf(3, uiState.upcomingGameList.size) -// ) -// ) -// Column { -// Text( -// text = "Game Schedule", -// style = heading1, -// modifier = Modifier -// .fillMaxWidth() -// .padding(horizontal = 24.dp, vertical = 8.dp) -// ) -// Spacer(modifier = Modifier.height(8.dp)) -// SportSelectorHeader( -// sports = uiState.selectionList, -// selectedGender = uiState.selectedGender, -// selectedSport = uiState.sportSelect, -// onGenderSelected = { -// homeViewModel.onGenderSelected(it) -// }, -// onSportSelected = { -// homeViewModel.onSportSelected(it) -// } -// ) -// } -// } -// -// is ApiResponse.Error -> { -// //TODO: Add Error screen -// Box( -// modifier = Modifier.fillMaxSize(), -// contentAlignment = Alignment.Center -// ) { -// Text( -// text = "Failed to load games. Please try again.", -// ) -// } -// } -// -// is ApiResponse.Success -> { -// UpcomingGamesCarousel(firstThreeGames) -// Column { -// Text( -// text = "Game Schedule", -// style = title, -// modifier = Modifier -// .fillMaxWidth() -// .padding(horizontal = 24.dp, vertical = 8.dp) -// ) -// Spacer(modifier = Modifier.height(8.dp)) -// SportSelectorHeader( -// sports = uiState.selectionList, -// selectedGender = uiState.selectedGender, -// selectedSport = uiState.sportSelect, -// onGenderSelected = { homeViewModel.onGenderSelected(it) }, -// onSportSelected = { homeViewModel.onSportSelected(it) } -// ) -// LazyColumn(modifier = Modifier.padding(horizontal = 24.dp)) { -// items(filteredGames.size) { page -> -// val game = filteredGames[page] -// SportCard( -// 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 -// ) -// } -// } -// } -// } -// } -// } -//} -// -//@Preview(showBackground = true) -//@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) -//@Composable -//private fun HomeScreenPreview() { -// HomeScreen() -//} 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 ac97dab..482d56f 100644 --- a/app/src/main/java/com/cornellappdev/score/util/DateUtil.kt +++ b/app/src/main/java/com/cornellappdev/score/util/DateUtil.kt @@ -5,9 +5,9 @@ import java.time.format.DateTimeFormatter; /** * Converts date of form String "month-abbr day (day-of-week)" (for example, "Apr 29 (Tue)") to a LocalDate object + * Returns null when parsing [strDate] fails */ - -fun formatDate(strDate: String): LocalDate? { +fun parseDate(strDate: String): LocalDate? { val subDate = strDate.substringBefore(" (") val formatter = DateTimeFormatter.ofPattern("MMM d") @@ -34,6 +34,5 @@ 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}" } diff --git a/app/src/main/java/com/cornellappdev/score/viewmodel/HomeViewModel.kt b/app/src/main/java/com/cornellappdev/score/viewmodel/HomeViewModel.kt index f90d87a..de74700 100644 --- a/app/src/main/java/com/cornellappdev/score/viewmodel/HomeViewModel.kt +++ b/app/src/main/java/com/cornellappdev/score/viewmodel/HomeViewModel.kt @@ -1,6 +1,5 @@ package com.cornellappdev.score.viewmodel -import android.util.Log import com.cornellappdev.score.R import com.cornellappdev.score.model.ApiResponse import com.cornellappdev.score.model.GameCardData @@ -11,7 +10,7 @@ import com.cornellappdev.score.model.SportSelection import com.cornellappdev.score.nav.root.RootNavigationViewModel import com.cornellappdev.score.util.dateToString import com.cornellappdev.score.util.formatColor -import com.cornellappdev.score.util.formatDate +import com.cornellappdev.score.util.parseDate import dagger.hilt.android.lifecycle.HiltViewModel import java.time.LocalDate import javax.inject.Inject @@ -22,6 +21,7 @@ data class HomeUiState( val selectionList: List, val loadedState: ApiResponse> ) { + //TODO: refactor filters to use flows - not best practice to expose original games list to the view val filteredGames: List get() = when (loadedState) { is ApiResponse.Success -> loadedState.data.filter { game -> @@ -32,11 +32,11 @@ data class HomeUiState( ApiResponse.Loading -> emptyList() ApiResponse.Error -> emptyList() } + val upcomingGames: List = filteredGames.take(3) } @HiltViewModel class HomeViewModel @Inject constructor( - //private val rootNavigationViewModel: RootNavigationViewModel, private val scoreRepository: ScoreRepository ) : BaseViewModel( HomeUiState( @@ -47,10 +47,7 @@ class HomeViewModel @Inject constructor( ) ) { init { - Log.d("HomeViewModel", "init reached") scoreRepository.fetchGames() - Log.d("HomeViewModel", "games fetched") - asyncCollect(scoreRepository.upcomingGamesFlow) { response -> applyMutation { copy( @@ -62,9 +59,9 @@ class HomeViewModel @Inject constructor( teamLogo = game.teamLogo, team = game.teamName, teamColor = formatColor(game.teamColor), - date = formatDate(game.date), - dateString = dateToString(formatDate(game.date)), - isLive = (LocalDate.now() == formatDate(game.date)), + date = parseDate(game.date), + dateString = dateToString(parseDate(game.date)), + isLive = (LocalDate.now() == parseDate(game.date)), location = game.city, gender = game.gender, genderIcon = if (game.gender == "Mens") R.drawable.ic_gender_men else R.drawable.ic_gender_women, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7124742..719d688 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,7 +12,6 @@ activity = "1.8.0" constraintlayout = "2.1.4" runtimeAndroid = "1.7.2" apollo = "4.1.1" -coreI18n = "1.0.0-alpha01" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -26,7 +25,6 @@ androidx-activity = { group = "androidx.activity", name = "activity", version.re androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } androidx-runtime-android = { group = "androidx.compose.runtime", name = "runtime-android", version.ref = "runtimeAndroid" } apollo-runtime = { module = "com.apollographql.apollo:apollo-runtime" } -androidx-core-i18n = { group = "androidx.core", name = "core-i18n", version.ref = "coreI18n" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } From e1ce60c3329b0b3349ded3b51605bce2950cc861 Mon Sep 17 00:00:00 2001 From: amjiao Date: Mon, 17 Mar 2025 10:48:29 -0400 Subject: [PATCH 11/19] HomeContent abstraction --- .../cornellappdev/score/screen/HomeScreen.kt | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) 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 25cd0e2..04f692e 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/HomeScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/HomeScreen.kt @@ -1,5 +1,6 @@ package com.cornellappdev.score.screen +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -16,6 +17,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -24,10 +26,10 @@ import com.cornellappdev.score.components.SportCard import com.cornellappdev.score.components.SportSelectorHeader import com.cornellappdev.score.components.UpcomingGamesCarousel import com.cornellappdev.score.model.ApiResponse -import com.cornellappdev.score.model.GameCardData import com.cornellappdev.score.model.GenderDivision import com.cornellappdev.score.model.SportSelection import com.cornellappdev.score.theme.Style.title +import com.cornellappdev.score.util.gameList import com.cornellappdev.score.viewmodel.HomeUiState import com.cornellappdev.score.viewmodel.HomeViewModel @@ -51,6 +53,7 @@ fun HomeScreen( CircularProgressIndicator() } } + is ApiResponse.Error -> { //TODO: Add Error screen Box( @@ -79,7 +82,7 @@ private fun HomeContent( uiState: HomeUiState, onGenderSelected: (GenderDivision) -> Unit, onSportSelected: (SportSelection) -> Unit - ){ +) { UpcomingGamesCarousel(uiState.upcomingGames) Column { Text( @@ -98,7 +101,7 @@ private fun HomeContent( onSportSelected = onSportSelected ) LazyColumn(modifier = Modifier.padding(horizontal = 24.dp)) { - items(uiState.filteredGames){ + items(uiState.filteredGames) { val game = it SportCard( teamLogo = game.teamLogo, @@ -118,6 +121,21 @@ private fun HomeContent( @Preview @Composable private fun HomeScreenPreview() { - //TODO: parameters?? - //HomeContent() + Column( + modifier = Modifier + .fillMaxSize() + .background(color = Color.White) + ) { + HomeContent( + HomeUiState( + selectedGender = GenderDivision.ALL, + sportSelect = SportSelection.All, + selectionList = emptyList(), + loadedState = ApiResponse.Success(gameList) + ), + onGenderSelected = {}, + onSportSelected = {} + ) + } } + From cd2e9eee279625eb6b46d0483f9b78ac4f5ce2e9 Mon Sep 17 00:00:00 2001 From: amjiao Date: Mon, 17 Mar 2025 12:37:05 -0400 Subject: [PATCH 12/19] Temp update --- .../score/components/UpcomingGamesCarousel.kt | 4 ++-- .../main/java/com/cornellappdev/score/model/Game.kt | 5 +++-- .../com/cornellappdev/score/model/ScoreRepository.kt | 3 ++- .../java/com/cornellappdev/score/util/DateUtil.kt | 12 ++---------- .../com/cornellappdev/score/util/TestingConstants.kt | 5 +++-- .../cornellappdev/score/viewmodel/HomeViewModel.kt | 8 +++----- 6 files changed, 15 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/score/components/UpcomingGamesCarousel.kt b/app/src/main/java/com/cornellappdev/score/components/UpcomingGamesCarousel.kt index c160ed1..0825066 100644 --- a/app/src/main/java/com/cornellappdev/score/components/UpcomingGamesCarousel.kt +++ b/app/src/main/java/com/cornellappdev/score/components/UpcomingGamesCarousel.kt @@ -87,8 +87,8 @@ fun UpcomingGamesCarousel(games: List) { location = game.location, modifier = Modifier, headerModifier = Modifier, - gradientColor1 = CornellRed, //TODO: is it okay if this is hardcoded - gradientColor2 = Color(game.teamColor) + gradientColor1 = CornellRed, + gradientColor2 = game.teamColor ) } 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 eb939d0..1c15f0c 100644 --- a/app/src/main/java/com/cornellappdev/score/model/Game.kt +++ b/app/src/main/java/com/cornellappdev/score/model/Game.kt @@ -1,5 +1,6 @@ package com.cornellappdev.score.model +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import java.time.LocalDate @@ -8,7 +9,7 @@ import java.time.LocalDate data class Game( val teamName: String, val teamLogo: String, - val teamColor: String, //should this also be a Color? + val teamColor: Int, //doesn't make senes for this to be a color since its data not ui stuff? val gender: String, val sport: String, val date: String, @@ -19,7 +20,7 @@ data class Game( data class GameCardData( val teamLogo: String, val team: String, - val teamColor: Int, + val teamColor: Color, val date: LocalDate?, val dateString: String, val isLive: Boolean, 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 d33fbc0..6b82e6c 100644 --- a/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt +++ b/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt @@ -10,6 +10,7 @@ import kotlinx.coroutines.launch import javax.inject.Inject import javax.inject.Singleton import com.cornellappdev.score.model.toResult +import com.cornellappdev.score.util.formatColor /** * This is a singleton responsible for fetching and caching all data for Score. @@ -46,7 +47,7 @@ class ScoreRepository @Inject constructor( Game( teamLogo = it, teamName = game.team.name, - teamColor = game.team.color, + teamColor = formatColor(game.team.color), //if we need to adjust alpha or something later, this should be an int) gender = game.gender, sport = game.sport, date = game.date, 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 482d56f..3a8cec3 100644 --- a/app/src/main/java/com/cornellappdev/score/util/DateUtil.kt +++ b/app/src/main/java/com/cornellappdev/score/util/DateUtil.kt @@ -9,7 +9,7 @@ import java.time.format.DateTimeFormatter; */ fun parseDate(strDate: String): LocalDate? { val subDate = strDate.substringBefore(" (") - val formatter = DateTimeFormatter.ofPattern("MMM d") + val formatter = DateTimeFormatter.ofPattern("MMM d yyyy") return try { LocalDate.parse("$subDate ${LocalDate.now().year}", formatter) //assumes current year @@ -27,12 +27,4 @@ fun formatColor(color: String): Int { return (alpha shl 24) or colorInt } -/** - * Takes in a LocalDate? object and returns it as a String of format "MM/DD/YYY" - */ -fun dateToString(date: LocalDate?): String { - if (date == null) { - return "--" - } - return "${date.month.value}/${date.dayOfMonth}/${date.year}" -} +val outputFormatter = DateTimeFormatter.ofPattern("M/d/yyyy") 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 cbf7547..ac4c45a 100644 --- a/app/src/main/java/com/cornellappdev/score/util/TestingConstants.kt +++ b/app/src/main/java/com/cornellappdev/score/util/TestingConstants.kt @@ -1,5 +1,6 @@ package com.cornellappdev.score.util +import androidx.compose.ui.graphics.Color import com.cornellappdev.score.R import com.cornellappdev.score.model.GameCardData import com.cornellappdev.score.model.GameData @@ -13,7 +14,7 @@ import java.time.LocalDate val PENN_GAME = GameCardData( teamLogo = "https://cornellbigred.com/images/logos/penn_200x200.png?width=80&height=80&mode=max", team = "Penn", - teamColor = 0x66B31B1B, + teamColor = Color(0x66B31B1B), date = LocalDate.now(), dateString = "3/1/25", isLive = false, @@ -27,7 +28,7 @@ val PENN_GAME = GameCardData( val PRINCETON_GAME = GameCardData( teamLogo = "https://cornellbigred.com/images/logos/Princeton_Tigers.png?width=80&height=80&mode=max", team = "Princeton", - teamColor = 0x66FF6000, + teamColor = Color(0x66FF6000), date = LocalDate.now(), dateString = "3/1/25", isLive = false, diff --git a/app/src/main/java/com/cornellappdev/score/viewmodel/HomeViewModel.kt b/app/src/main/java/com/cornellappdev/score/viewmodel/HomeViewModel.kt index de74700..019a1dc 100644 --- a/app/src/main/java/com/cornellappdev/score/viewmodel/HomeViewModel.kt +++ b/app/src/main/java/com/cornellappdev/score/viewmodel/HomeViewModel.kt @@ -1,5 +1,6 @@ package com.cornellappdev.score.viewmodel +import androidx.compose.ui.graphics.Color import com.cornellappdev.score.R import com.cornellappdev.score.model.ApiResponse import com.cornellappdev.score.model.GameCardData @@ -8,9 +9,6 @@ import com.cornellappdev.score.model.ScoreRepository import com.cornellappdev.score.model.Sport import com.cornellappdev.score.model.SportSelection import com.cornellappdev.score.nav.root.RootNavigationViewModel -import com.cornellappdev.score.util.dateToString -import com.cornellappdev.score.util.formatColor -import com.cornellappdev.score.util.parseDate import dagger.hilt.android.lifecycle.HiltViewModel import java.time.LocalDate import javax.inject.Inject @@ -58,9 +56,9 @@ class HomeViewModel @Inject constructor( GameCardData( teamLogo = game.teamLogo, team = game.teamName, - teamColor = formatColor(game.teamColor), + teamColor = Color(game.teamColor), date = parseDate(game.date), - dateString = dateToString(parseDate(game.date)), + dateString = parseDate(game.date)?.format(outputFormatter) ?: game.date, isLive = (LocalDate.now() == parseDate(game.date)), location = game.city, gender = game.gender, From 8b6c36a6c62f4d357c9951a9cea640099f794363 Mon Sep 17 00:00:00 2001 From: amjiao Date: Mon, 17 Mar 2025 14:01:06 -0400 Subject: [PATCH 13/19] Date fixes --- .../java/com/cornellappdev/score/viewmodel/HomeViewModel.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/java/com/cornellappdev/score/viewmodel/HomeViewModel.kt b/app/src/main/java/com/cornellappdev/score/viewmodel/HomeViewModel.kt index 019a1dc..c644c63 100644 --- a/app/src/main/java/com/cornellappdev/score/viewmodel/HomeViewModel.kt +++ b/app/src/main/java/com/cornellappdev/score/viewmodel/HomeViewModel.kt @@ -12,6 +12,8 @@ import com.cornellappdev.score.nav.root.RootNavigationViewModel import dagger.hilt.android.lifecycle.HiltViewModel import java.time.LocalDate import javax.inject.Inject +import com.cornellappdev.score.util.parseDate +import com.cornellappdev.score.util.outputFormatter data class HomeUiState( val selectedGender: GenderDivision, @@ -68,6 +70,9 @@ class HomeViewModel @Inject constructor( ?: R.drawable.ic_empty_placeholder ) } + .filter{game -> + game.date?.isAfter(LocalDate.now().minusDays(1)) ?: false + } .sortedBy { it.date } ApiResponse.Success(gameCards) } From ae435eaac072fa3a139b13f78391af85ff4c022d Mon Sep 17 00:00:00 2001 From: amjiao Date: Mon, 17 Mar 2025 19:48:31 -0400 Subject: [PATCH 14/19] Refactored HomeViewModel with map and toGameCardData --- .../score/model/DataFetchResponse.kt | 9 ++++- .../com/cornellappdev/score/model/Game.kt | 22 +++++++++- .../score/viewmodel/HomeViewModel.kt | 40 ++++--------------- 3 files changed, 36 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/score/model/DataFetchResponse.kt b/app/src/main/java/com/cornellappdev/score/model/DataFetchResponse.kt index 67900bb..c515de5 100644 --- a/app/src/main/java/com/cornellappdev/score/model/DataFetchResponse.kt +++ b/app/src/main/java/com/cornellappdev/score/model/DataFetchResponse.kt @@ -11,4 +11,11 @@ sealed class ApiResponse { data object Error : ApiResponse() data class Success(val data: T) : ApiResponse() -} \ No newline at end of file +} + +fun ApiResponse.map(transform: (T) -> R): ApiResponse = + when (this) { + ApiResponse.Error -> ApiResponse.Error + ApiResponse.Loading -> ApiResponse.Loading + is ApiResponse.Success -> ApiResponse.Success(transform(this.data)) + } \ 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 1c15f0c..2fa19a2 100644 --- a/app/src/main/java/com/cornellappdev/score/model/Game.kt +++ b/app/src/main/java/com/cornellappdev/score/model/Game.kt @@ -2,6 +2,9 @@ package com.cornellappdev.score.model import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter +import com.cornellappdev.score.R +import com.cornellappdev.score.util.outputFormatter +import com.cornellappdev.score.util.parseDate import java.time.LocalDate // TODO Refactor to make easier to filter... actual gender, etc. @@ -14,7 +17,24 @@ data class Game( val sport: String, val date: String, val city: String -) +){ + val toGameCardData = + GameCardData( + teamLogo = teamLogo, + team = teamName, + teamColor = Color(teamColor), + date = parseDate(date), + dateString = parseDate(date)?.format(outputFormatter) + ?: date, + isLive = (LocalDate.now() == parseDate(date)), + location = city, + 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 + ) +} //Data for HomeScreen game displays data class GameCardData( diff --git a/app/src/main/java/com/cornellappdev/score/viewmodel/HomeViewModel.kt b/app/src/main/java/com/cornellappdev/score/viewmodel/HomeViewModel.kt index c644c63..b2d713e 100644 --- a/app/src/main/java/com/cornellappdev/score/viewmodel/HomeViewModel.kt +++ b/app/src/main/java/com/cornellappdev/score/viewmodel/HomeViewModel.kt @@ -1,19 +1,15 @@ package com.cornellappdev.score.viewmodel -import androidx.compose.ui.graphics.Color -import com.cornellappdev.score.R import com.cornellappdev.score.model.ApiResponse import com.cornellappdev.score.model.GameCardData import com.cornellappdev.score.model.GenderDivision import com.cornellappdev.score.model.ScoreRepository import com.cornellappdev.score.model.Sport import com.cornellappdev.score.model.SportSelection -import com.cornellappdev.score.nav.root.RootNavigationViewModel +import com.cornellappdev.score.model.map import dagger.hilt.android.lifecycle.HiltViewModel import java.time.LocalDate import javax.inject.Inject -import com.cornellappdev.score.util.parseDate -import com.cornellappdev.score.util.outputFormatter data class HomeUiState( val selectedGender: GenderDivision, @@ -51,34 +47,12 @@ class HomeViewModel @Inject constructor( asyncCollect(scoreRepository.upcomingGamesFlow) { response -> applyMutation { copy( - loadedState = when (response) { - is ApiResponse.Success -> { - val gameCards = response.data - .map { game -> - GameCardData( - teamLogo = game.teamLogo, - team = game.teamName, - teamColor = Color(game.teamColor), - date = parseDate(game.date), - dateString = parseDate(game.date)?.format(outputFormatter) ?: game.date, - isLive = (LocalDate.now() == parseDate(game.date)), - location = game.city, - gender = game.gender, - genderIcon = if (game.gender == "Mens") R.drawable.ic_gender_men else R.drawable.ic_gender_women, - sport = game.sport, - sportIcon = Sport.fromDisplayName(game.sport)?.emptyIcon - ?: R.drawable.ic_empty_placeholder - ) - } - .filter{game -> - game.date?.isAfter(LocalDate.now().minusDays(1)) ?: false - } - .sortedBy { it.date } - ApiResponse.Success(gameCards) - } - - ApiResponse.Loading -> ApiResponse.Loading - ApiResponse.Error -> ApiResponse.Error + loadedState = response.map { list -> + list.map { game -> + game.toGameCardData + }.filter { game -> + game.date?.isAfter(LocalDate.now().minusDays(1)) ?: false + }.sortedBy { it.date } } ) } From d245b66f5e36eace1ee97c194e4247a4b2b28012 Mon Sep 17 00:00:00 2001 From: amjiao Date: Mon, 17 Mar 2025 20:01:44 -0400 Subject: [PATCH 15/19] Cleanup --- .../main/java/com/cornellappdev/score/model/Game.kt | 5 ++--- .../com/cornellappdev/score/model/ScoreRepository.kt | 10 ++++------ 2 files changed, 6 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 2fa19a2..b6f642c 100644 --- a/app/src/main/java/com/cornellappdev/score/model/Game.kt +++ b/app/src/main/java/com/cornellappdev/score/model/Game.kt @@ -1,7 +1,6 @@ package com.cornellappdev.score.model import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.painter.Painter import com.cornellappdev.score.R import com.cornellappdev.score.util.outputFormatter import com.cornellappdev.score.util.parseDate @@ -12,12 +11,12 @@ import java.time.LocalDate data class Game( val teamName: String, val teamLogo: String, - val teamColor: Int, //doesn't make senes for this to be a color since its data not ui stuff? + val teamColor: Int, val gender: String, val sport: String, val date: String, val city: String -){ +) { val toGameCardData = GameCardData( teamLogo = teamLogo, 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 6b82e6c..577dce8 100644 --- a/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt +++ b/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt @@ -2,6 +2,7 @@ package com.cornellappdev.score.model import android.util.Log import com.apollographql.apollo.ApolloClient +import com.cornellappdev.score.util.formatColor import com.example.score.GamesQuery import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow @@ -9,8 +10,6 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import javax.inject.Inject import javax.inject.Singleton -import com.cornellappdev.score.model.toResult -import com.cornellappdev.score.util.formatColor /** * This is a singleton responsible for fetching and caching all data for Score. @@ -21,7 +20,6 @@ import com.cornellappdev.score.util.formatColor @Singleton class ScoreRepository @Inject constructor( private val apolloClient: ApolloClient, - // TODO use this to launch queries private val appScope: CoroutineScope, ) { private val _upcomingGamesFlow = @@ -38,7 +36,7 @@ class ScoreRepository @Inject constructor( try { val result = (apolloClient.query(GamesQuery()).execute()).toResult() - if(result.isSuccess){ + if (result.isSuccess) { val games = result.getOrNull() val upcomingGameslist: List = @@ -54,9 +52,9 @@ class ScoreRepository @Inject constructor( city = game.city ) } - } ?: emptyList() + } ?: emptyList() _upcomingGamesFlow.value = ApiResponse.Success(upcomingGameslist) - }else{ + } else { _upcomingGamesFlow.value = ApiResponse.Error } From 5261c04bd1c29143622d0aa3deac45992a2ece9e Mon Sep 17 00:00:00 2001 From: amjiao Date: Mon, 17 Mar 2025 20:08:17 -0400 Subject: [PATCH 16/19] Documentation + cleanup --- .../java/com/cornellappdev/score/model/ScoreRepository.kt | 2 +- app/src/main/java/com/cornellappdev/score/util/DateUtil.kt | 7 ++++++- 2 files changed, 7 insertions(+), 2 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 577dce8..444eeeb 100644 --- a/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt +++ b/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt @@ -45,7 +45,7 @@ class ScoreRepository @Inject constructor( Game( teamLogo = it, teamName = game.team.name, - teamColor = formatColor(game.team.color), //if we need to adjust alpha or something later, this should be an int) + teamColor = formatColor(game.team.color), gender = game.gender, sport = game.sport, date = game.date, 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 3a8cec3..f8c1538 100644 --- a/app/src/main/java/com/cornellappdev/score/util/DateUtil.kt +++ b/app/src/main/java/com/cornellappdev/score/util/DateUtil.kt @@ -18,6 +18,11 @@ fun parseDate(strDate: String): LocalDate? { } } +/** + * DateTimeFormatter of pattern "M/d/yyyy" + */ +val outputFormatter = DateTimeFormatter.ofPattern("M/d/yyyy") + /** * Converts from format "#xxxxxx" to a valid hex, with alpha = 40. Ready to be passed into Color() */ @@ -27,4 +32,4 @@ fun formatColor(color: String): Int { return (alpha shl 24) or colorInt } -val outputFormatter = DateTimeFormatter.ofPattern("M/d/yyyy") + From b15ede03083e2e4c919dfcfd481d4ec76ed4079e Mon Sep 17 00:00:00 2001 From: amjiao Date: Mon, 17 Mar 2025 20:15:37 -0400 Subject: [PATCH 17/19] formatColor relocation --- .../com/cornellappdev/score/model/ScoreRepository.kt | 12 ++++++++++-- .../java/com/cornellappdev/score/util/DateUtil.kt | 10 ---------- 2 files changed, 10 insertions(+), 12 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 444eeeb..beaae01 100644 --- a/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt +++ b/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt @@ -2,7 +2,6 @@ package com.cornellappdev.score.model import android.util.Log import com.apollographql.apollo.ApolloClient -import com.cornellappdev.score.util.formatColor import com.example.score.GamesQuery import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow @@ -63,4 +62,13 @@ class ScoreRepository @Inject constructor( _upcomingGamesFlow.value = ApiResponse.Error } } -} \ No newline at end of file +} + +/** + * Converts from format "#xxxxxx" to a valid hex, with alpha = 40. Ready to be passed into Color() + */ +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 +} 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 f8c1538..c517879 100644 --- a/app/src/main/java/com/cornellappdev/score/util/DateUtil.kt +++ b/app/src/main/java/com/cornellappdev/score/util/DateUtil.kt @@ -23,13 +23,3 @@ fun parseDate(strDate: String): LocalDate? { */ val outputFormatter = DateTimeFormatter.ofPattern("M/d/yyyy") -/** - * Converts from format "#xxxxxx" to a valid hex, with alpha = 40. Ready to be passed into Color() - */ -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 -} - - From d18f9aeabb9e70fa8a4690d34c9fe7b7a3abbdae Mon Sep 17 00:00:00 2001 From: amjiao Date: Wed, 19 Mar 2025 15:06:05 -0400 Subject: [PATCH 18/19] Revisions --- .../score/components/SportCard.kt | 2 +- .../com/cornellappdev/score/model/Game.kt | 41 ++++++++++--------- .../score/model/ScoreRepository.kt | 12 +----- .../cornellappdev/score/screen/HomeScreen.kt | 3 +- .../com/cornellappdev/score/util/ColorUtil.kt | 11 +++++ .../com/cornellappdev/score/util/DateUtil.kt | 2 +- .../score/viewmodel/HomeViewModel.kt | 3 +- 7 files changed, 40 insertions(+), 34 deletions(-) create mode 100644 app/src/main/java/com/cornellappdev/score/util/ColorUtil.kt diff --git a/app/src/main/java/com/cornellappdev/score/components/SportCard.kt b/app/src/main/java/com/cornellappdev/score/components/SportCard.kt index e0aae8e..d0edb7b 100644 --- a/app/src/main/java/com/cornellappdev/score/components/SportCard.kt +++ b/app/src/main/java/com/cornellappdev/score/components/SportCard.kt @@ -107,7 +107,7 @@ fun SportCard( model = teamLogo, modifier = Modifier .height(20.dp) - .padding(start = 4.dp, end = 4.dp), + .padding(horizontal = 4.dp), contentDescription = "" ) 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 b6f642c..bfdbd78 100644 --- a/app/src/main/java/com/cornellappdev/score/model/Game.kt +++ b/app/src/main/java/com/cornellappdev/score/model/Game.kt @@ -3,7 +3,7 @@ package com.cornellappdev.score.model import androidx.compose.ui.graphics.Color import com.cornellappdev.score.R import com.cornellappdev.score.util.outputFormatter -import com.cornellappdev.score.util.parseDate +import com.cornellappdev.score.util.parseDateOrNull import java.time.LocalDate // TODO Refactor to make easier to filter... actual gender, etc. @@ -11,29 +11,12 @@ import java.time.LocalDate data class Game( val teamName: String, val teamLogo: String, - val teamColor: Int, + val teamColor: Color, val gender: String, val sport: String, val date: String, val city: String -) { - val toGameCardData = - GameCardData( - teamLogo = teamLogo, - team = teamName, - teamColor = Color(teamColor), - date = parseDate(date), - dateString = parseDate(date)?.format(outputFormatter) - ?: date, - isLive = (LocalDate.now() == parseDate(date)), - location = city, - 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 - ) -} +) //Data for HomeScreen game displays data class GameCardData( @@ -118,4 +101,22 @@ enum class GameStatus { NOT_STARTED, IN_PROGRESS, COMPLETED +} + +fun Game.toGameCardData(): GameCardData{ + return GameCardData( + teamLogo = teamLogo, + team = teamName, + teamColor = teamColor, + date = parseDateOrNull(date), + dateString = parseDateOrNull(date)?.format(outputFormatter) + ?: date, + isLive = (LocalDate.now() == parseDateOrNull(date)), + location = city, + 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 + ) } \ No newline at end of file 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 beaae01..f15f496 100644 --- a/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt +++ b/app/src/main/java/com/cornellappdev/score/model/ScoreRepository.kt @@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.asStateFlow 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. @@ -44,7 +45,7 @@ class ScoreRepository @Inject constructor( Game( teamLogo = it, teamName = game.team.name, - teamColor = formatColor(game.team.color), + teamColor = parseColor(game.team.color).copy(alpha = 0.4f*255), gender = game.gender, sport = game.sport, date = game.date, @@ -63,12 +64,3 @@ class ScoreRepository @Inject constructor( } } } - -/** - * Converts from format "#xxxxxx" to a valid hex, with alpha = 40. Ready to be passed into Color() - */ -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 -} 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 04f692e..c8c6478 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/HomeScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/HomeScreen.kt @@ -30,6 +30,7 @@ import com.cornellappdev.score.model.GenderDivision import com.cornellappdev.score.model.SportSelection import com.cornellappdev.score.theme.Style.title import com.cornellappdev.score.util.gameList +import com.cornellappdev.score.util.sportSelectionList import com.cornellappdev.score.viewmodel.HomeUiState import com.cornellappdev.score.viewmodel.HomeViewModel @@ -130,7 +131,7 @@ private fun HomeScreenPreview() { HomeUiState( selectedGender = GenderDivision.ALL, sportSelect = SportSelection.All, - selectionList = emptyList(), + selectionList = sportSelectionList, loadedState = ApiResponse.Success(gameList) ), onGenderSelected = {}, diff --git a/app/src/main/java/com/cornellappdev/score/util/ColorUtil.kt b/app/src/main/java/com/cornellappdev/score/util/ColorUtil.kt new file mode 100644 index 0000000..89e66bb --- /dev/null +++ b/app/src/main/java/com/cornellappdev/score/util/ColorUtil.kt @@ -0,0 +1,11 @@ +package com.cornellappdev.score.util + +import androidx.compose.ui.graphics.Color + +/** + * Converts a hexcode String into Color object + */ +fun parseColor(color: String): Color { + val colorInt = Integer.parseInt(color.removePrefix("#"), 16) + return Color(colorInt) +} \ 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 c517879..0fe3def 100644 --- a/app/src/main/java/com/cornellappdev/score/util/DateUtil.kt +++ b/app/src/main/java/com/cornellappdev/score/util/DateUtil.kt @@ -7,7 +7,7 @@ import java.time.format.DateTimeFormatter; * Converts date of form String "month-abbr day (day-of-week)" (for example, "Apr 29 (Tue)") to a LocalDate object * Returns null when parsing [strDate] fails */ -fun parseDate(strDate: String): LocalDate? { +fun parseDateOrNull(strDate: String): LocalDate? { val subDate = strDate.substringBefore(" (") val formatter = DateTimeFormatter.ofPattern("MMM d yyyy") diff --git a/app/src/main/java/com/cornellappdev/score/viewmodel/HomeViewModel.kt b/app/src/main/java/com/cornellappdev/score/viewmodel/HomeViewModel.kt index b2d713e..2121b70 100644 --- a/app/src/main/java/com/cornellappdev/score/viewmodel/HomeViewModel.kt +++ b/app/src/main/java/com/cornellappdev/score/viewmodel/HomeViewModel.kt @@ -7,6 +7,7 @@ import com.cornellappdev.score.model.ScoreRepository import com.cornellappdev.score.model.Sport import com.cornellappdev.score.model.SportSelection import com.cornellappdev.score.model.map +import com.cornellappdev.score.model.toGameCardData import dagger.hilt.android.lifecycle.HiltViewModel import java.time.LocalDate import javax.inject.Inject @@ -49,7 +50,7 @@ class HomeViewModel @Inject constructor( copy( loadedState = response.map { list -> list.map { game -> - game.toGameCardData + game.toGameCardData() }.filter { game -> game.date?.isAfter(LocalDate.now().minusDays(1)) ?: false }.sortedBy { it.date } From d5590e0a0d26814db7d126c9f81620d706bf71dd Mon Sep 17 00:00:00 2001 From: amjiao Date: Wed, 19 Mar 2025 15:18:21 -0400 Subject: [PATCH 19/19] Revert agp version --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 719d688..d8b8921 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.7.3" +agp = "8.8.2" composeLintChecks = "1.4.2" kotlin = "1.9.0" coreKtx = "1.10.1"