diff --git a/app/src/main/java/com/cornellappdev/score/components/highlights/ArticleHighlightsCard.kt b/app/src/main/java/com/cornellappdev/score/components/highlights/ArticleHighlightsCard.kt index fac49a3f..8d81f8d5 100644 --- a/app/src/main/java/com/cornellappdev/score/components/highlights/ArticleHighlightsCard.kt +++ b/app/src/main/java/com/cornellappdev/score/components/highlights/ArticleHighlightsCard.kt @@ -1,6 +1,7 @@ package com.cornellappdev.score.components.highlights import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -18,6 +19,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.text.TextStyle import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale @@ -47,13 +49,15 @@ fun ArticleHighlightCard( linkTextStyle: TextStyle = TextStyle.Default, imageOverlayAlpha: Float = 0.4f ) { + val uriHandler = LocalUriHandler.current + Box( modifier = Modifier .then(if (isWideFormat) Modifier.fillMaxWidth() else Modifier.width(241.dp)) .height(192.dp) .clip(shape = RoundedCornerShape(12.dp)) + .clickable { uriHandler.openUri(articleHighlight.articleUrl) } ) { - //todo: empty state if image doesn't load AsyncImage( model = articleHighlight.imageUrl, contentDescription = "highlight image", @@ -100,7 +104,7 @@ fun ArticleHighlightCard( Text( color = Color.White, style = labelsNormal, - text = articleHighlight.date + text = articleHighlight.dateString ) } } @@ -115,6 +119,7 @@ private fun ArticleHighlightCardPreview() { "Late Goal Lifts No. 6 Men’s Hockey Over Brown", "maxresdefault.jpg", "https://cornellsun.com/article/london-mcdavid-is-making-a-name-for-herself-at-cornell", + null, "11/9", Sport.ICE_HOCKEY ), @@ -130,6 +135,7 @@ private fun WideArticleHighlightCardPreview() { "Late Goal Lifts No. 6 Men’s Hockey Over Brown", "maxresdefault.jpg", "https://cornellsun.com/article/london-mcdavid-is-making-a-name-for-herself-at-cornell", + null, "11/9", Sport.ICE_HOCKEY ), diff --git a/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsCardLazyColumn.kt b/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsCardLazyColumn.kt index 2a9d968e..134fdce9 100644 --- a/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsCardLazyColumn.kt +++ b/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsCardLazyColumn.kt @@ -26,49 +26,30 @@ import com.cornellappdev.score.theme.Style.bodyNormal import com.cornellappdev.score.util.highlightsList import com.cornellappdev.score.util.recentSearchList -sealed interface SearchResultsState { - data object Recent : SearchResultsState - data class Results(val items: List) : SearchResultsState - data object Empty : SearchResultsState -} +enum class SearchUiState { RECENT, EMPTY, RESULTS } @Composable fun HighlightsCardLazyColumn( recentSearchList: List, query: String, - highlightsList: List, - onItemClick: () -> Unit, - onCloseClick: () -> Unit, + filteredResults: List, + onItemClick: (String) -> Unit, + onCloseClick: (String) -> Unit, numResultsHeader: (@Composable () -> Unit)? = null ) { Column( modifier = Modifier.padding(horizontal = 24.dp) ) { - /*todo: move to VM*/ - val resultsState: SearchResultsState = - when { - recentSearchList.isNotEmpty() && query.isEmpty() -> - SearchResultsState.Recent - - query.isNotEmpty() -> { - val filtered = highlightsList.filter { - it.title.contains(query, ignoreCase = true) - } - - if (filtered.isEmpty()) { - SearchResultsState.Empty - } else { - SearchResultsState.Results(filtered) - } - } - - else -> SearchResultsState.Recent - } + val uiStateKey = when { + query.isEmpty() -> SearchUiState.RECENT + filteredResults.isEmpty() -> SearchUiState.EMPTY + else -> SearchUiState.RESULTS + } AnimatedContent( - targetState = resultsState, + targetState = uiStateKey, transitionSpec = { (fadeIn() + slideInVertically { it / 8 }) togetherWith (fadeOut() + slideOutVertically { -it / 8 }) @@ -77,7 +58,7 @@ fun HighlightsCardLazyColumn( ) { state -> when (state) { - SearchResultsState.Recent -> { + SearchUiState.RECENT -> { RecentSearches( recentSearchList, onItemClick, @@ -85,21 +66,21 @@ fun HighlightsCardLazyColumn( ) } - SearchResultsState.Empty -> { + SearchUiState.EMPTY -> { EmptyStateBox( icon = R.drawable.ic_kid_star, title = "No results yet." ) } - is SearchResultsState.Results -> { + SearchUiState.RESULTS -> { Column { numResultsHeader?.invoke() LazyColumn( verticalArrangement = Arrangement.spacedBy(16.dp) ) { - items(state.items) { item -> + items(filteredResults) { item -> when (item) { is HighlightData.Video -> VideoHighlightCard(item.data, true) diff --git a/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsCardRow.kt b/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsCardRow.kt index 57755f66..72c3d17b 100644 --- a/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsCardRow.kt +++ b/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsCardRow.kt @@ -23,6 +23,7 @@ import androidx.compose.ui.unit.dp import com.cornellappdev.score.R import com.cornellappdev.score.components.ScorePreview import com.cornellappdev.score.model.HighlightData +import com.cornellappdev.score.screen.HighlightsSubScreenType import com.cornellappdev.score.theme.Style.bodyNormal import com.cornellappdev.score.theme.Style.heading2 import com.cornellappdev.score.util.highlightsList @@ -30,7 +31,8 @@ import com.cornellappdev.score.util.highlightsList @Composable fun HighlightsCardRow( highlightsList: List, - rowHeader: String + rowHeader: String, + toSubScreen: (HighlightsSubScreenType) -> Unit ) { Column( modifier = Modifier.fillMaxWidth() @@ -47,7 +49,7 @@ fun HighlightsCardRow( style = heading2 ) Row( - modifier = Modifier.clickable {/*todo navigation to Today screen*/ }, + modifier = Modifier.clickable { toSubScreen(if (rowHeader == "Today") HighlightsSubScreenType.TODAY else HighlightsSubScreenType.PAST3DAYS) }, verticalAlignment = Alignment.CenterVertically ) { Text( @@ -82,6 +84,6 @@ fun HighlightsCardRow( @Composable private fun HighlightsCardRowPreview() { ScorePreview { - HighlightsCardRow(highlightsList, "Today") + HighlightsCardRow(highlightsList, "Today", {}) } } diff --git a/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsFilter.kt b/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsFilter.kt index ea273ffe..2c92ccaf 100644 --- a/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsFilter.kt +++ b/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsFilter.kt @@ -27,28 +27,30 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.cornellappdev.score.model.Sport +import com.cornellappdev.score.model.SportSelection import com.cornellappdev.score.theme.GrayLight import com.cornellappdev.score.theme.GrayPrimary import com.cornellappdev.score.theme.Stroke import com.cornellappdev.score.theme.Style.bodyNormal import com.cornellappdev.score.theme.White -import com.cornellappdev.score.util.sportList +import com.cornellappdev.score.util.sportSelectionList @Composable private fun HighlightsFilterButton( sport: Sport, - onFilterSelected: (Sport) -> Unit, + onFilterSelected: (SportSelection) -> Unit, isSelected: Boolean = false, ) { OutlinedButton( modifier = Modifier .height(32.dp), border = BorderStroke(width = 1.dp, color = Stroke), - onClick = { onFilterSelected(sport) }, + onClick = { onFilterSelected(SportSelection.SportSelect(sport)) }, shape = RoundedCornerShape(100.dp), colors = outlinedButtonColors( containerColor = if (isSelected) GrayLight else White, - contentColor = GrayPrimary + contentColor = GrayPrimary, + disabledContainerColor = GrayLight ), contentPadding = PaddingValues(horizontal = 12.dp, vertical = 4.dp), ) { @@ -67,20 +69,29 @@ private fun HighlightsFilterButton( @Composable fun HighlightsFilterRow( - sportList: List, - onFilterSelected: (Sport) -> Unit, + sportList: List, + selectedSport: SportSelection, + onFilterSelected: (SportSelection) -> Unit, ) { LazyRow( - modifier = Modifier - .fillMaxWidth(), + modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp), + contentPadding = PaddingValues(start = 24.dp, end = 24.dp), verticalAlignment = Alignment.CenterVertically ) { - item { Spacer(Modifier.width(12.dp)) } - items(sportList) { item -> - HighlightsFilterButton(item, onFilterSelected) + items( + items = sportList.filterIsInstance(), + key = { it.sport } + ) { selection -> + + val isSelected = selectedSport == selection + + HighlightsFilterButton( + sport = selection.sport, + onFilterSelected = onFilterSelected, + isSelected = isSelected + ) } - item { Spacer(Modifier.width(12.dp)) } } } @@ -88,11 +99,11 @@ fun HighlightsFilterRow( @Composable private fun HighlightsFilterButtonPreview() { var isSelected by remember { mutableStateOf(false) } - HighlightsFilterButton(Sport.BASEBALL, { isSelected = !isSelected }, isSelected = isSelected) + HighlightsFilterButton(Sport.BASEBALL, { !isSelected }, isSelected = isSelected) } @Preview @Composable private fun HighlightsFilterRowPreview() { - HighlightsFilterRow(sportList, {}) + HighlightsFilterRow(sportSelectionList, SportSelection.All, {}) } diff --git a/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsLoadingScreen.kt b/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsLoadingScreen.kt new file mode 100644 index 00000000..649164a9 --- /dev/null +++ b/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsLoadingScreen.kt @@ -0,0 +1,185 @@ +package com.cornellappdev.score.components.highlights + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.cornellappdev.score.components.LoadingStateBox +import com.cornellappdev.score.components.ScorePreview +import com.cornellappdev.score.screen.HighlightsSubScreenHeader +import com.cornellappdev.score.theme.GrayStroke +import com.cornellappdev.score.theme.Style.heading1 +import com.cornellappdev.score.theme.Style.heading2 + +@Composable +fun HighlightsLoadingScreen( + topHeader: String, modifier: Modifier = Modifier +) { + Column( + modifier = modifier.fillMaxSize() + ) { + Column( + modifier = Modifier.padding(horizontal = 24.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = topHeader, + style = heading1, + color = GrayStroke, + modifier = Modifier + .fillMaxWidth() + .align(Alignment.Start) + ) + Spacer(modifier = Modifier.height(16.dp)) + LoadingStateBox(100, 40.dp) + } + Spacer(modifier = Modifier.height(16.dp)) + Row( + modifier = Modifier.padding(start = 24.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + repeat(4) { + LoadingStateBox( + 100, 30.dp, modifier = Modifier.width(85.dp) + ) + } + } + Spacer(modifier = Modifier.height(24.dp)) + Row( + modifier = Modifier + .padding(horizontal = 24.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Loading Today...", style = heading2, color = GrayStroke + ) + LoadingStateBox( + 100, 15.dp, modifier = Modifier.width( + 50.dp + ) + ) + } + Spacer(modifier = Modifier.height(16.dp)) + Row( + modifier = Modifier.padding(start = 24.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + LoadingStateBox( + 12, 192.dp, modifier = Modifier.width( + 241.dp + ) + ) + LoadingStateBox( + 12, 192.dp, modifier = Modifier.width( + 241.dp + ) + ) + } + Spacer(modifier = Modifier.height(24.dp)) + Row( + modifier = Modifier + .padding(horizontal = 24.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Loading Past 3 Days...", style = heading2, color = GrayStroke + ) + LoadingStateBox( + 100, 15.dp, modifier = Modifier.width( + 50.dp + ) + ) + } + Spacer(modifier = Modifier.height(16.dp)) + Row( + modifier = Modifier.padding(start = 24.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + LoadingStateBox( + 12, 192.dp, modifier = Modifier.width( + 241.dp + ) + ) + LoadingStateBox( + 12, 192.dp, modifier = Modifier.width( + 241.dp + ) + ) + } + } +} + +@Composable +fun SubHighlightsLoadingScreen( + header: String, modifier: Modifier = Modifier +) { + Column( + modifier = Modifier.fillMaxWidth() + ) { + HighlightsSubScreenHeader( + header = header, navigateBack = {}) + Column( + modifier = Modifier + .fillMaxWidth() + .padding(start = 24.dp, end = 24.dp, top = 24.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + LoadingStateBox( + 100, height = 40.dp + ) + + Spacer(modifier = Modifier.height(16.dp)) + } + Row( + modifier = Modifier.padding(start = 24.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + repeat(4) { + LoadingStateBox( + 100, 30.dp, modifier = Modifier.width(85.dp) + ) + } + } + Spacer(modifier.height(24.dp)) + Column( + modifier = Modifier.padding(horizontal = 24.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + repeat(4) { + LoadingStateBox( + 12, 192.dp + ) + } + } + } +} + + +@Preview +@Composable +private fun HighlightsLoadingScreenPreview() = + ScorePreview{ + HighlightsLoadingScreen("Loading Highlights...") + } + +@Preview +@Composable +private fun SubHighlightsLoadingScreenPreview() = + ScorePreview { + SubHighlightsLoadingScreen("Past 3 Days") + } \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsScreenSearchFilterBar.kt b/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsScreenSearchFilterBar.kt index d5344b8a..130dd748 100644 --- a/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsScreenSearchFilterBar.kt +++ b/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsScreenSearchFilterBar.kt @@ -10,17 +10,29 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.cornellappdev.score.components.ScorePreview -import com.cornellappdev.score.model.Sport -import com.cornellappdev.score.util.sportList +import com.cornellappdev.score.model.SportSelection +import com.cornellappdev.score.util.sportSelectionList @Composable fun HighlightsScreenSearchFilterBar( - sportList: List + sportList: List, + query: String, + selectedSport: SportSelection, + onQueryChange: (String) -> Unit, + onSearch: (String) -> Unit, + onFilterSelected: (SportSelection) -> Unit, + navigateBack: () -> Unit ) { Column(modifier = Modifier.fillMaxWidth()) { - HighlightsSearchBar(modifier = Modifier.padding(horizontal = 24.dp)) + HighlightsSearchBar( + modifier = Modifier.padding(horizontal = 24.dp), + query = query, + onQueryChange = onQueryChange, + onSearch = onSearch, + navigateBack = navigateBack + ) Spacer(modifier = Modifier.height(16.dp)) - HighlightsFilterRow(sportList, {/*todo on filter selected*/ }) + HighlightsFilterRow(sportList, selectedSport, onFilterSelected) } } @@ -28,6 +40,13 @@ fun HighlightsScreenSearchFilterBar( @Composable private fun HighlightsScreenSearchFilterBarPreview() { ScorePreview { - HighlightsScreenSearchFilterBar(sportList) + HighlightsScreenSearchFilterBar( + sportSelectionList, + "", + SportSelection.All, + {}, + {}, + onFilterSelected = {}, + navigateBack = {}) } } \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsSearchBar.kt b/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsSearchBar.kt index 2297e777..6d1f0c31 100644 --- a/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsSearchBar.kt +++ b/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsSearchBar.kt @@ -12,12 +12,12 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -34,7 +34,9 @@ import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -43,19 +45,24 @@ import com.cornellappdev.score.theme.GrayLight import com.cornellappdev.score.theme.Style.bodyMedium import com.cornellappdev.score.theme.Style.bodyNormal -private fun Modifier.highlightsSearchRowModifier(): Modifier = this - .fillMaxWidth() - .background(Color.White, RoundedCornerShape(100.dp)) - .border(1.dp, GrayLight, RoundedCornerShape(100.dp)) - .clip(RoundedCornerShape(100.dp)) - .padding(horizontal = 16.dp, vertical = 8.dp) +private fun Modifier.highlightsSearchRowModifier(): Modifier = + this + .fillMaxWidth() + .background(Color.White, RoundedCornerShape(100.dp)) + .border(1.dp, GrayLight, RoundedCornerShape(100.dp)) + .clip(RoundedCornerShape(100.dp)) + .padding(horizontal = 8.dp, vertical = 8.dp) @Composable fun HighlightsSearchBar( + query: String, + onQueryChange: (String) -> Unit, + onSearch: (String) -> Unit, modifier: Modifier = Modifier, + navigateBack: () -> Unit ) { + val keyboardController = LocalSoftwareKeyboardController.current val interactionSource = remember { MutableInteractionSource() } - var searchQuery by remember { mutableStateOf("") } //todo: to be handled by viewmodel var isFocused by remember { mutableStateOf(true) } val focusManager = LocalFocusManager.current @@ -71,42 +78,48 @@ fun HighlightsSearchBar( modifier = modifier ) { BasicTextField( - value = searchQuery, - onValueChange = { searchQuery = it /*todo viewmodel load results*/ }, + value = query, + onValueChange = onQueryChange, singleLine = true, textStyle = bodyNormal, visualTransformation = VisualTransformation.None, interactionSource = interactionSource, enabled = true, + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.Search + ), + keyboardActions = KeyboardActions( + onSearch = { + onSearch(query) + keyboardController?.hide() + focusManager.clearFocus() + }), modifier = Modifier .focusRequester(focusRequester) .weight(1f) .background(Color.Transparent) .onFocusChanged { focusState -> - isFocused = focusState.isFocused /* todo - consider making this an onFocus function in VM */ + isFocused = + focusState.isFocused /* todo - consider making this an onFocus function in VM */ }, decorationBox = { innerTextField -> Row( - modifier = - Modifier - .highlightsSearchRowModifier() - .padding(horizontal = 8.dp), + modifier = Modifier.highlightsSearchRowModifier(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { Row( verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center + horizontalArrangement = Arrangement.spacedBy(4.dp) ) { Icon( painter = painterResource(R.drawable.search), contentDescription = "search icon", tint = Color.Unspecified ) - Spacer(Modifier.width(8.dp)) Box { innerTextField() - if (searchQuery.isEmpty()) { + if (query.isEmpty()) { Text( text = "Search keywords", style = bodyNormal.copy(color = Color.Gray) @@ -116,7 +129,7 @@ fun HighlightsSearchBar( } AnimatedVisibility( - visible = searchQuery.isNotEmpty(), + visible = query.isNotEmpty(), enter = fadeIn() + scaleIn(), exit = fadeOut() + scaleOut() ) { @@ -124,27 +137,22 @@ fun HighlightsSearchBar( painter = painterResource(R.drawable.ic_close), contentDescription = "clear field", modifier = Modifier.clickable( - onClick = { searchQuery = "" } - ) - ) + onClick = { onQueryChange("") })) } } - } - ) + }) AnimatedVisibility( isFocused ) { Text( - "Cancel", - style = bodyMedium, - modifier = Modifier.clickable { - isFocused = false; - focusManager.clearFocus(force = true); - searchQuery = "" - /*todo: clear the text in the search bar*/ - } - ) + "Cancel", style = bodyMedium, modifier = Modifier.clickable( + onClick = { + isFocused = false + focusManager.clearFocus(force = true) + onQueryChange("") + navigateBack() + })) } } } @@ -152,18 +160,14 @@ fun HighlightsSearchBar( /*HighlightsSearchEntryPointRow is the non-functional version of the HighlightsSearchBar, it's a dummy component that's clickable in HighlightsScreen and will navigate to HighlightsSearchScreen */ @Composable fun HighlightsSearchEntryPointRow( - onClick: () -> Unit, - modifier: Modifier = Modifier + onClick: () -> Unit, modifier: Modifier = Modifier ) { - Row( - modifier = - Modifier - .highlightsSearchRowModifier() - .clickable { onClick() } - .then(modifier), + Row(modifier = Modifier + .highlightsSearchRowModifier() + .clickable { onClick() } + .then(modifier), verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { + horizontalArrangement = Arrangement.spacedBy(4.dp)) { Icon( painter = painterResource(R.drawable.search), contentDescription = "search icon", @@ -171,8 +175,7 @@ fun HighlightsSearchEntryPointRow( ) Text( - text = "Search keywords", - style = bodyNormal.copy(color = Color.Gray) + text = "Search keywords", style = bodyNormal.copy(color = Color.Gray) ) } } @@ -186,5 +189,5 @@ private fun HighlightsSearchEntryPointRowPreview() { @Preview @Composable private fun HighlightsSearchBarPreview() { - HighlightsSearchBar() + HighlightsSearchBar(query = "", onQueryChange = {}, onSearch = {}, navigateBack = {}) } \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/score/components/highlights/RecentSearches.kt b/app/src/main/java/com/cornellappdev/score/components/highlights/RecentSearches.kt index 460c37af..123bcc2e 100644 --- a/app/src/main/java/com/cornellappdev/score/components/highlights/RecentSearches.kt +++ b/app/src/main/java/com/cornellappdev/score/components/highlights/RecentSearches.kt @@ -25,15 +25,15 @@ import com.cornellappdev.score.theme.Style.metricSmallNormal @Composable private fun RecentSearchItem( query: String, - onItemClick: () -> Unit, - onCloseClick: () -> Unit + onItemClick: (String) -> Unit, + onCloseClick: (String) -> Unit ) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier .fillMaxWidth() - .clickable(onClick = { onItemClick() /*search the query*/ }) + .clickable(onClick = { onItemClick(query) /*search the query*/ }) ) { Row( verticalAlignment = Alignment.CenterVertically, @@ -48,7 +48,7 @@ private fun RecentSearchItem( } IconButton( - onClick = { onCloseClick() /*delete this search from the recent searches list*/ }, + onClick = { onCloseClick(query) /*delete this search from the recent searches list*/ }, modifier = Modifier.size(10.dp) ) { Icon( @@ -63,8 +63,8 @@ private fun RecentSearchItem( @Composable fun RecentSearches( recentQueriesList: List, - onItemClick: () -> Unit, - onCloseClick: () -> Unit + onItemClick: (String) -> Unit, + onCloseClick: (String) -> Unit ) { Column( modifier = Modifier diff --git a/app/src/main/java/com/cornellappdev/score/components/highlights/VideoHighlightsCard.kt b/app/src/main/java/com/cornellappdev/score/components/highlights/VideoHighlightsCard.kt index b8257145..54086620 100644 --- a/app/src/main/java/com/cornellappdev/score/components/highlights/VideoHighlightsCard.kt +++ b/app/src/main/java/com/cornellappdev/score/components/highlights/VideoHighlightsCard.kt @@ -1,6 +1,7 @@ package com.cornellappdev.score.components.highlights import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -20,6 +21,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.text.TextStyle import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale @@ -50,12 +52,12 @@ private fun VideoHighlightCardHeader( .fillMaxWidth() .height(117.dp) ) { - //todo: empty state if image doesn't load AsyncImage( model = imageUrl, contentDescription = "Highlight article image", - modifier = Modifier.fillMaxSize(), - contentScale = ContentScale.Crop + contentScale = ContentScale.Crop, + modifier = Modifier + .fillMaxWidth() ) Box( modifier = Modifier @@ -99,17 +101,27 @@ fun VideoHighlightCardBody( Row( verticalAlignment = Alignment.CenterVertically ) { - Icon( - painter = painterResource(videoHighlight.sport.emptyIcon), - contentDescription = "Sport icon", - modifier = Modifier.size(24.dp), - tint = Color.Unspecified - ) - Icon( - painter = painterResource(if (videoHighlight.gender == GenderDivision.FEMALE) R.drawable.ic_gender_women else R.drawable.ic_gender_men), - contentDescription = "Gender icon", - tint = Color.Unspecified - ) + videoHighlight.sport?.let { sport -> + Icon( + painter = painterResource(sport.emptyIcon), + contentDescription = "Sport icon", + modifier = Modifier.size(24.dp), + tint = Color.Unspecified + ) + } + + videoHighlight.gender?.let { gender -> + val iconRes = when (gender) { + GenderDivision.FEMALE -> R.drawable.ic_gender_women + else -> R.drawable.ic_gender_men + } + + Icon( + painter = painterResource(iconRes), + contentDescription = "Gender icon", + tint = Color.Unspecified + ) + } } } Spacer(Modifier.height(8.dp)) @@ -128,7 +140,7 @@ fun VideoHighlightCardBody( } Text( style = labelsNormal, - text = videoHighlight.date + text = videoHighlight.dateString ) } } @@ -142,10 +154,13 @@ fun VideoHighlightCard( linkTextStyle: TextStyle = TextStyle.Default, headerOverlay: @Composable () -> Unit = {} ) { + val uriHandler = LocalUriHandler.current + Column( modifier = modifier .then(if (isWideFormat) Modifier.fillMaxWidth() else Modifier.width(241.dp)) .clip(RoundedCornerShape(12.dp)) + .clickable { uriHandler.openUri(videoHighlight.videoUrl) } ) { VideoHighlightCardHeader(videoHighlight.thumbnailImageUrl, headerOverlay) VideoHighlightCardBody(videoHighlight, isWideFormat, linkTextStyle) @@ -161,20 +176,24 @@ class VideoHighlightsPreviewProvider : PreviewParameterProvider = sequence { val samples = listOf( VideoHighlightData( - "vs Columbia", - "maxresdefault.jpg", - "https://cornellsun.com/article/london-mcdavid-is-making-a-name-for-herself-at-cornell", - "11/9", - Sport.BASEBALL, - GenderDivision.MALE + title = "vs Columbia", + thumbnailImageUrl = "maxresdefault.jpg", + videoUrl = "https://cornellsun.com/article/london-mcdavid-is-making-a-name-for-herself-at-cornell", + date = null, + dateString = "11/9/25", + sport = Sport.BASEBALL, + gender = GenderDivision.MALE, + duration = "0:44" ), VideoHighlightData( - "Late Goal Lifts No. 6 Men’s Hockey Over Brown", - "maxresdefault.jpg", - "https://cornellsun.com/article/london-mcdavid-is-making-a-name-for-herself-at-cornell", - "11/9", - Sport.BASEBALL, - GenderDivision.MALE + title = "Late Goal Lifts No. 6 Men’s Hockey Over Brown", + thumbnailImageUrl = "maxresdefault.jpg", + videoUrl = "https://cornellsun.com/article/london-mcdavid-is-making-a-name-for-herself-at-cornell", + date = null, + dateString = "11/9/25", + sport = Sport.BASEBALL, + gender = GenderDivision.MALE, + duration = "0:44" ) ) for (sample in samples) { diff --git a/app/src/main/java/com/cornellappdev/score/nav/ScoreNavHost.kt b/app/src/main/java/com/cornellappdev/score/nav/ScoreNavHost.kt index ba66aa07..0481a2e9 100644 --- a/app/src/main/java/com/cornellappdev/score/nav/ScoreNavHost.kt +++ b/app/src/main/java/com/cornellappdev/score/nav/ScoreNavHost.kt @@ -14,11 +14,10 @@ import com.cornellappdev.score.nav.root.ScoreScreens.Home import com.cornellappdev.score.screen.GameDetailsScreen import com.cornellappdev.score.screen.HighlightsScreen import com.cornellappdev.score.screen.HighlightsSearchScreen +import com.cornellappdev.score.screen.HighlightsSubScreen +import com.cornellappdev.score.screen.HighlightsSubScreenType import com.cornellappdev.score.screen.HomeScreen import com.cornellappdev.score.screen.PastGamesScreen -import com.cornellappdev.score.util.highlightsList -import com.cornellappdev.score.util.recentSearchList -import com.cornellappdev.score.util.sportList import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.json.Json @@ -67,26 +66,52 @@ fun ScoreNavHost(navController: NavHostController) { navController.navigateUp() }) } -//// removed for 2/2026 release -// composable { backStackEntry -> -// CompositionLocalProvider(LocalViewModelStoreOwner provides mainScreenViewModelStoreOwner) { -// HighlightsScreen(toSearchScreen = { navController.navigate(ScoreScreens.HighlightsSearchScreen) }) -// } -// } -// -// composable { backStackEntry -> -// CompositionLocalProvider(LocalViewModelStoreOwner provides mainScreenViewModelStoreOwner) { -// HighlightsSearchScreen( -// sportList = sportList, -// recentSearchList = recentSearchList, -// highlightsList = highlightsList, -// query = "", -// header = "Search all highlights", -// {}, -// {} -// ) -// } -// } + + composable { + CompositionLocalProvider(LocalViewModelStoreOwner provides mainScreenViewModelStoreOwner) { + HighlightsScreen( + toSearchScreen = { + navController.navigate( + ScoreScreens.HighlightsSearchScreen( + HighlightsSubScreenType.ALL + ) + ) + }, + toSubScreen = { subScreenType -> + navController.navigate(ScoreScreens.HighlightsSubScreen(subScreenType)) + }) + } + } + + composable { backStackEntry -> + val route = backStackEntry.toRoute() + val subScreenType = route.subScreenType + + HighlightsSubScreen( + toSearchScreen = { + navController.navigate( + ScoreScreens.HighlightsSearchScreen(subScreenType) + ) + }, + navigateBack = { navController.popBackStack() }, + subScreenType = subScreenType + ) + } + + composable { backStackEntry -> + CompositionLocalProvider(LocalViewModelStoreOwner provides mainScreenViewModelStoreOwner) { + + val route = + backStackEntry.toRoute() + + val searchScreenType = route.subScreenType + + HighlightsSearchScreen( + searchScreenType = searchScreenType, + navigateBack = { navController.popBackStack() } + ) + } + } } } 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 8542ff75..c727d18e 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 @@ -28,6 +28,7 @@ import androidx.navigation.toRoute import com.cornellappdev.score.R import com.cornellappdev.score.nav.ScoreNavHost import com.cornellappdev.score.nav.ScoreNavigationBar +import com.cornellappdev.score.screen.HighlightsSubScreenType import com.cornellappdev.score.theme.LocalInfiniteLoading import com.cornellappdev.score.theme.White import kotlinx.serialization.Serializable @@ -110,12 +111,18 @@ sealed class ScoreScreens { @Serializable data class GameScoreSummaryPage(val scoreEvents: String) : ScoreScreens() -////removed for 2/2026 release -// @Serializable -// data object HighlightsScreen : ScoreScreens() -// -// @Serializable -// data object HighlightsSearchScreen : ScoreScreens() + @Serializable + data object HighlightsScreen : ScoreScreens() + + @Serializable + data class HighlightsSubScreen( + val subScreenType: HighlightsSubScreenType + ) : ScoreScreens() + + @Serializable + data class HighlightsSearchScreen( + val subScreenType: HighlightsSubScreenType + ) : ScoreScreens() } fun NavBackStackEntry.toScreen(): ScoreScreens? = @@ -124,9 +131,9 @@ fun NavBackStackEntry.toScreen(): ScoreScreens? = "GameDetailsPage" -> toRoute() "ScoresScreen" -> toRoute() "GameScoreSummaryPage" -> toRoute() - //removed for 2/2026 release -// "HighlightsScreen" -> toRoute() -// "HighlightsSearchScreen" -> toRoute() + "HighlightsScreen" -> toRoute() + "HighlightsSearchScreen" -> toRoute() + "HighlightsSubScreen" -> toRoute() else -> throw IllegalArgumentException("Invalid screen") } @@ -144,13 +151,12 @@ val tabs = listOf( selectedIcon = R.drawable.ic_schedule_filled, screen = ScoreScreens.Home, ), - //removed for 2/2026 review -// NavItem( -// label = "Highlights", -// unselectedIcon = R.drawable.ic_nav_star, -// selectedIcon = R.drawable.ic_nav_star_filled, -// screen = ScoreScreens.HighlightsScreen, -// ), + NavItem( + label = "Highlights", + unselectedIcon = R.drawable.ic_nav_star, + selectedIcon = R.drawable.ic_nav_star_filled, + screen = ScoreScreens.HighlightsScreen, + ), NavItem( label = "Scores", unselectedIcon = R.drawable.ic_scores, diff --git a/app/src/main/java/com/cornellappdev/score/screen/HighlightsScreen.kt b/app/src/main/java/com/cornellappdev/score/screen/HighlightsScreen.kt index c3e30b00..220bae37 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/HighlightsScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/HighlightsScreen.kt @@ -6,6 +6,8 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -14,31 +16,88 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel import com.cornellappdev.score.R import com.cornellappdev.score.components.EmptyStateBox +import com.cornellappdev.score.components.ErrorState +import com.cornellappdev.score.components.highlights.HighlightsLoadingScreen +import com.cornellappdev.score.components.LoadingScreen import com.cornellappdev.score.components.ScorePreview +import com.cornellappdev.score.components.ScorePullToRefreshBox import com.cornellappdev.score.components.highlights.HighlightsCardRow import com.cornellappdev.score.components.highlights.HighlightsFilterRow import com.cornellappdev.score.components.highlights.HighlightsSearchEntryPointRow +import com.cornellappdev.score.model.ApiResponse import com.cornellappdev.score.model.HighlightData -import com.cornellappdev.score.model.Sport +import com.cornellappdev.score.model.SportSelection import com.cornellappdev.score.theme.Style.heading1 import com.cornellappdev.score.util.highlightsList -import com.cornellappdev.score.util.sportList +import com.cornellappdev.score.util.sportSelectionList +import com.cornellappdev.score.viewmodel.HighlightsViewModel +import kotlinx.serialization.Serializable @Composable fun HighlightsScreen( - sportList: List = emptyList(), //note - emptyLists are placeholders for nav to work, will replace will viewModel - todayHighlightsList: List = emptyList(), - pastThreeHighlightsList: List = emptyList(), - toSearchScreen: () -> Unit + toSearchScreen: () -> Unit, + highlightsViewModel: HighlightsViewModel = hiltViewModel(), + toSubScreen: (HighlightsSubScreenType) -> Unit ) { + val uiState = highlightsViewModel.collectUiStateValue() + Column( modifier = Modifier .fillMaxSize() .background(color = Color.White) + .padding(top = 24.dp) + ) { + when (uiState.loadedState) { + is ApiResponse.Loading -> { + HighlightsLoadingScreen("Loading Highlights...") + } + + is ApiResponse.Error -> { + ErrorState({ highlightsViewModel.onRefresh() }, "Oops! Highlights failed to load.") + } + + is ApiResponse.Success -> { + ScorePullToRefreshBox( + isRefreshing = uiState.isRefreshing, + { highlightsViewModel.onRefresh() } + ) { + HighlightsScreenContent( + selectedSport = uiState.sportSelect, + sportList = uiState.sportSelectionList, + onSportSelected = { highlightsViewModel.onSportSelected(it) }, + todayHighlightsList = uiState.todayHighlights, + pastThreeHighlightsList = uiState.pastThreeDaysHighlights, + toSearchScreen = toSearchScreen, + toSubScreen = toSubScreen + ) + } + } + } + } +} + +@Serializable +enum class HighlightsSubScreenType { + TODAY, PAST3DAYS, ALL +} + +@Composable +private fun HighlightsScreenContent( + selectedSport: SportSelection, + onSportSelected: (SportSelection) -> Unit, + sportList: List, + todayHighlightsList: List, + pastThreeHighlightsList: List, + toSearchScreen: () -> Unit, + toSubScreen: (HighlightsSubScreenType) -> Unit +) { + Column( + modifier = Modifier.fillMaxSize() + .verticalScroll(rememberScrollState()) ) { - Spacer(modifier = Modifier.height(24.dp)) Column( modifier = Modifier.padding(horizontal = 24.dp) ) { @@ -47,7 +106,7 @@ fun HighlightsScreen( HighlightsSearchEntryPointRow(toSearchScreen) } Spacer(modifier = Modifier.height(16.dp)) - HighlightsFilterRow(sportList, { /*todo: handle with viewmodel*/ }) + HighlightsFilterRow(sportList, selectedSport, onSportSelected) Spacer(modifier = Modifier.height(24.dp)) if (todayHighlightsList.isEmpty() && pastThreeHighlightsList.isEmpty()) { EmptyStateBox( @@ -56,25 +115,26 @@ fun HighlightsScreen( ) } if (todayHighlightsList.isNotEmpty()) { - HighlightsCardRow(todayHighlightsList, "Today") + HighlightsCardRow(todayHighlightsList, "Today", toSubScreen) } if (pastThreeHighlightsList.isNotEmpty()) { - HighlightsCardRow(pastThreeHighlightsList, "Past 3 days") + HighlightsCardRow(pastThreeHighlightsList, "Past 3 days", toSubScreen) } } } + data class HighlightsScreenPreviewData( - val sportList: List, + val sportList: List, val todayHighlightList: List, val pastHighlightList: List ) class HighlightsScreenPreviewProvider : PreviewParameterProvider { override val values: Sequence = sequence { - yield(HighlightsScreenPreviewData(sportList, highlightsList, highlightsList)) - yield(HighlightsScreenPreviewData(sportList, emptyList(), emptyList())) - yield(HighlightsScreenPreviewData(sportList, emptyList(), highlightsList)) + yield(HighlightsScreenPreviewData(sportSelectionList, highlightsList, highlightsList)) + yield(HighlightsScreenPreviewData(sportSelectionList, emptyList(), emptyList())) + yield(HighlightsScreenPreviewData(sportSelectionList, emptyList(), highlightsList)) } } @@ -84,11 +144,13 @@ private fun HighlightScreenPreview( @PreviewParameter(HighlightsScreenPreviewProvider::class) previewData: HighlightsScreenPreviewData ) { ScorePreview { - HighlightsScreen( + HighlightsScreenContent( + selectedSport = SportSelection.All, sportList = previewData.sportList, todayHighlightsList = previewData.todayHighlightList, pastThreeHighlightsList = previewData.pastHighlightList, - toSearchScreen = {} - ) + toSearchScreen = {}, + onSportSelected = {}, + toSubScreen = {}) } } \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/score/screen/HighlightsSearchScreen.kt b/app/src/main/java/com/cornellappdev/score/screen/HighlightsSearchScreen.kt index 0fb0bc8d..30cd321c 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/HighlightsSearchScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/HighlightsSearchScreen.kt @@ -8,36 +8,70 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel import com.cornellappdev.score.components.ScorePreview import com.cornellappdev.score.components.highlights.HighlightsCardLazyColumn import com.cornellappdev.score.components.highlights.HighlightsCardLazyColumnResultsHeader import com.cornellappdev.score.components.highlights.HighlightsScreenSearchFilterBar import com.cornellappdev.score.model.HighlightData -import com.cornellappdev.score.model.Sport +import com.cornellappdev.score.model.SportSelection import com.cornellappdev.score.theme.Style.heading2 import com.cornellappdev.score.util.highlightsList import com.cornellappdev.score.util.recentSearchList -import com.cornellappdev.score.util.sportList +import com.cornellappdev.score.util.sportSelectionList +import com.cornellappdev.score.viewmodel.HighlightsViewModel + @Composable fun HighlightsSearchScreen( - sportList: List, + navigateBack: () -> Unit, + searchScreenType: HighlightsSubScreenType, + highlightsViewModel: HighlightsViewModel = hiltViewModel() +) { + val uiState = highlightsViewModel.collectUiStateValue() + + val (header, filteredResults) = when (searchScreenType) { + HighlightsSubScreenType.TODAY -> "Search today" to uiState.todayHighlights + HighlightsSubScreenType.PAST3DAYS -> "Search past 3 days" to uiState.pastThreeDaysHighlights + HighlightsSubScreenType.ALL -> "Search all highlights" to uiState.filteredHighlights + } + + HighlightsSearchScreenContent( + sportList = uiState.sportSelectionList, + onFilterSelected = { highlightsViewModel.onSportSelected(it) }, + recentSearchList = uiState.recentSearches, + filteredResults = filteredResults, + query = uiState.query, + selectedFilter = uiState.sportSelect, + onQueryChange = { highlightsViewModel.onQueryChange(it) }, + onSearch = { highlightsViewModel.onSearch(it) }, + header = header, + onItemClick = { highlightsViewModel.onSearchRecent(it) }, + onCloseClick = { highlightsViewModel.onRemoveRecent(it) }, + navigateBack = navigateBack + ) +} + +@Composable +fun HighlightsSearchScreenContent( + sportList: List, + onFilterSelected: (SportSelection) -> Unit, recentSearchList: List, - highlightsList: List, + filteredResults: List, query: String, + selectedFilter: SportSelection, + onQueryChange: (String) -> Unit, + onSearch: (String) -> Unit, header: String, - onItemClick: () -> Unit, - onCloseClick: () -> Unit + onItemClick: (String) -> Unit, + onCloseClick: (String) -> Unit, + navigateBack: () -> Unit ) { Column( modifier = Modifier @@ -49,29 +83,35 @@ fun HighlightsSearchScreen( Spacer(modifier = Modifier.height(16.dp)) HighlightsScreenSearchFilterBar( - sportList + sportList, + query, + selectedFilter, + onQueryChange, + onSearch, + onFilterSelected, + navigateBack ) Spacer(modifier = Modifier.height(24.dp)) HighlightsCardLazyColumn( recentSearchList, query, - highlightsList, onItemClick, onCloseClick, - { HighlightsCardLazyColumnResultsHeader(highlightsList.size) }) + filteredResults, + onItemClick, + onCloseClick, + { HighlightsCardLazyColumnResultsHeader(filteredResults.size) }) } } data class HighlightsSearchScreenPreviewData( - val sportList: List, - val recentSearchList: List, - val query: String + val sportList: List, val recentSearchList: List, val query: String ) class HighlightsSearchScreenPreviewProvider : PreviewParameterProvider { override val values: Sequence = sequence { - yield(HighlightsSearchScreenPreviewData(sportList, recentSearchList, "")) - yield(HighlightsSearchScreenPreviewData(sportList, recentSearchList, "Sports")) - yield(HighlightsSearchScreenPreviewData(sportList, recentSearchList, "Hockey")) + yield(HighlightsSearchScreenPreviewData(sportSelectionList, recentSearchList, "")) + yield(HighlightsSearchScreenPreviewData(sportSelectionList, recentSearchList, "Sports")) + yield(HighlightsSearchScreenPreviewData(sportSelectionList, recentSearchList, "Hockey")) } } @@ -81,14 +121,18 @@ private fun HighlightScreenPreview( @PreviewParameter(HighlightsSearchScreenPreviewProvider::class) previewData: HighlightsSearchScreenPreviewData ) { ScorePreview { - HighlightsSearchScreen( + HighlightsSearchScreenContent( sportList = previewData.sportList, + onFilterSelected = {}, recentSearchList = previewData.recentSearchList, - highlightsList = highlightsList, + filteredResults = highlightsList, query = previewData.query, + selectedFilter = SportSelection.All, + onQueryChange = {}, + onSearch = {}, header = "Search All Highlights", onItemClick = {}, - onCloseClick = {} - ) + onCloseClick = {}, + navigateBack = {}) } } \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/score/screen/HighlightsSubScreen.kt b/app/src/main/java/com/cornellappdev/score/screen/HighlightsSubScreen.kt index 2ec4a401..20849246 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/HighlightsSubScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/HighlightsSubScreen.kt @@ -3,18 +3,21 @@ package com.cornellappdev.score.screen import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.material3.Icon import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.dropShadow import androidx.compose.ui.graphics.Color @@ -24,20 +27,27 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel import com.cornellappdev.score.R +import com.cornellappdev.score.components.ErrorState import com.cornellappdev.score.components.ScorePreview -import com.cornellappdev.score.components.highlights.HighlightsCardLazyColumn -import com.cornellappdev.score.components.highlights.HighlightsScreenSearchFilterBar +import com.cornellappdev.score.components.ScorePullToRefreshBox +import com.cornellappdev.score.components.highlights.ArticleHighlightCard +import com.cornellappdev.score.components.highlights.HighlightsFilterRow +import com.cornellappdev.score.components.highlights.HighlightsSearchEntryPointRow +import com.cornellappdev.score.components.highlights.SubHighlightsLoadingScreen +import com.cornellappdev.score.components.highlights.VideoHighlightCard +import com.cornellappdev.score.model.ApiResponse import com.cornellappdev.score.model.HighlightData -import com.cornellappdev.score.model.Sport +import com.cornellappdev.score.model.SportSelection import com.cornellappdev.score.theme.Style.heading2 import com.cornellappdev.score.theme.White import com.cornellappdev.score.util.highlightsList -import com.cornellappdev.score.util.recentSearchList -import com.cornellappdev.score.util.sportList +import com.cornellappdev.score.util.sportSelectionList +import com.cornellappdev.score.viewmodel.HighlightsViewModel @Composable -private fun HighlightsSubScreenHeader( +fun HighlightsSubScreenHeader( header: String, navigateBack: () -> Unit ) { @@ -54,18 +64,19 @@ private fun HighlightsSubScreenHeader( ), color = White ) { - Row( + Box( modifier = Modifier .padding(horizontal = 24.dp, vertical = 12.dp) - .fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(96.dp) + .fillMaxWidth() ) { Icon( painter = painterResource(R.drawable.ic_left_arrowhead), contentDescription = "back arrow", - modifier = Modifier.clickable(onClick = { navigateBack() }) + modifier = Modifier + .clickable(onClick = { navigateBack() }) + .align(Alignment.CenterStart) ) - Text(header, style = heading2) + Text(header, style = heading2, modifier = Modifier.align(Alignment.Center)) } } } @@ -80,44 +91,115 @@ private fun HighlightsSubScreenHeaderPreview() { @Composable fun HighlightsSubScreen( - sportList: List, - recentSearchList: List, + navigateBack: () -> Unit, + toSearchScreen: () -> Unit, + subScreenType: HighlightsSubScreenType, + highlightsViewModel: HighlightsViewModel = hiltViewModel() +) { + val uiState = highlightsViewModel.collectUiStateValue() + + Column( + modifier = Modifier + .fillMaxSize() + .background(color = Color.White) + ) { + val (highlightsList, header) = when (subScreenType) { + HighlightsSubScreenType.TODAY -> + uiState.todayHighlights to "Today" + + HighlightsSubScreenType.PAST3DAYS -> + uiState.pastThreeDaysHighlights to "Past 3 Days" + + HighlightsSubScreenType.ALL -> + uiState.filteredHighlights to "All highlights" + } + + when (uiState.loadedState) { + is ApiResponse.Loading -> { + SubHighlightsLoadingScreen(header) + } + + is ApiResponse.Error -> { + ErrorState({ highlightsViewModel.onRefresh() }, "Oops! Highlights failed to load.") + } + + is ApiResponse.Success -> { + ScorePullToRefreshBox( + isRefreshing = uiState.loadedState == ApiResponse.Loading, + { highlightsViewModel.onRefresh() } + ) { + HighlightsSubScreenContent( + selectedSport = uiState.sportSelect, + sportList = uiState.sportSelectionList, + onFilterSelected = { highlightsViewModel.onSportSelected(it) }, + highlightsList = highlightsList, + header = header, + navigateBack = navigateBack, + toSearchScreen = toSearchScreen + ) + } + } + } + } +} + +@Composable +fun HighlightsSubScreenContent( + selectedSport: SportSelection, + sportList: List, + onFilterSelected: (SportSelection) -> Unit, highlightsList: List, - query: String, header: String, - onItemClick: () -> Unit, - onCloseClick: () -> Unit + navigateBack: () -> Unit, + toSearchScreen: () -> Unit, ) { Column( modifier = Modifier .fillMaxSize() .background(color = Color.White) - .padding(top = 24.dp) + //.padding(top = 24.dp) ) { - HighlightsSubScreenHeader(header, {}) + HighlightsSubScreenHeader(header, navigateBack) + Spacer(modifier = Modifier.height(24.dp)) + Column( + modifier = Modifier.padding(horizontal = 24.dp) + ) { + HighlightsSearchEntryPointRow({ toSearchScreen() }) + } Spacer(modifier = Modifier.height(16.dp)) - HighlightsScreenSearchFilterBar( - sportList + HighlightsFilterRow( + sportList, + selectedSport = selectedSport, + onFilterSelected = onFilterSelected ) + + Spacer(modifier = Modifier.height(24.dp)) - HighlightsCardLazyColumn( - recentSearchList, - query, - highlightsList, - onItemClick, - onCloseClick - ) + LazyColumn( + Modifier.padding(horizontal = 24.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + items(highlightsList) { item -> + when (item) { + is HighlightData.Video -> + VideoHighlightCard(item.data, true) + + is HighlightData.Article -> + ArticleHighlightCard(item.data, true) + } + } + } } } @Preview @Composable private fun HighlightsSubScreenPreview() { - HighlightsSubScreen( - sportList = sportList, - recentSearchList = recentSearchList, + HighlightsSubScreenContent( + selectedSport = SportSelection.All, + sportList = sportSelectionList, + onFilterSelected = {}, highlightsList = highlightsList, - query = "s", header = "Past 3 Days", {}, {} ) 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 1536efad..5029bab2 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/HomeScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/HomeScreen.kt @@ -74,7 +74,7 @@ fun HomeScreen( } is ApiResponse.Error -> { - ErrorState({ homeViewModel.onRefresh() }, "Oops! Schedules failed to load.") + ErrorState({homeViewModel.onRefresh() }, "Oops! Schedules failed to load.") } is ApiResponse.Success -> { diff --git a/app/src/main/java/com/cornellappdev/score/screen/ProfileSampleData.kt b/app/src/main/java/com/cornellappdev/score/screen/ProfileSampleData.kt index 15f819e9..419b4821 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/ProfileSampleData.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/ProfileSampleData.kt @@ -24,7 +24,8 @@ val sampleProfileData = ProfileData( title = "vs. Columbia", thumbnailImageUrl = "android.resource://com.cornellappdev.score/${R.drawable.profile_highlight_soccer}", videoUrl = "https://www.youtube.com/@CornellAthletics", - date = "11/09", + dateString = "11/09", + date = null, sport = Sport.BASEBALL, gender = GenderDivision.MALE, duration = "1:25", @@ -37,7 +38,8 @@ val sampleProfileData = ProfileData( title = "Late Goal Lifts No. 6 Men’s Hockey Over Brown", imageUrl = "android.resource://com.cornellappdev.score/${R.drawable.profile_highlight_hockey}", articleUrl = "https://cornellsun.com/", - date = "11/09", + date = null, + dateString = "11/09", sport = Sport.ICE_HOCKEY ) ) 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 a726a125..858cc23a 100644 --- a/app/src/main/java/com/cornellappdev/score/util/TestingConstants.kt +++ b/app/src/main/java/com/cornellappdev/score/util/TestingConstants.kt @@ -231,45 +231,53 @@ val recentSearchList = listOf("Columbia", "Men's ice hockey", "Late goal lifts N val highlightsList = listOf( HighlightData.Video (VideoHighlightData( - "vs Columbia", - "maxresdefault.jpg", - "https://cornellsun.com/article/london-mcdavid-is-making-a-name-for-herself-at-cornell", - "11/09", - Sport.BASEBALL, - GenderDivision.MALE + title = "vs Columbia", + thumbnailImageUrl = "maxresdefault.jpg", + videoUrl = "https://cornellsun.com/article/london-mcdavid-is-making-a-name-for-herself-at-cornell", + date = null, + dateString = "11/09", + sport = Sport.BASEBALL, + gender = GenderDivision.MALE, + duration = "0:44" )), HighlightData.Article (ArticleHighlightData( "Late Goal Lifts No. 6 Men’s Hockey Over Brown", "maxresdefault.jpg", "https://cornellsun.com/article/london-mcdavid-is-making-a-name-for-herself-at-cornell", + null, "11/09", Sport.ICE_HOCKEY )), HighlightData.Video (VideoHighlightData( - "vs Columbia", - "maxresdefault.jpg", - "https://cornellsun.com/article/london-mcdavid-is-making-a-name-for-herself-at-cornell", - "11/9", - Sport.BASEBALL, - GenderDivision.MALE + title = "vs Columbia", + thumbnailImageUrl = "maxresdefault.jpg", + videoUrl = "https://cornellsun.com/article/london-mcdavid-is-making-a-name-for-herself-at-cornell", + date = null, + dateString = "11/9", + sport = Sport.BASEBALL, + gender = GenderDivision.MALE, + duration = "0:44" )), HighlightData.Article (ArticleHighlightData( "Late Goal Lifts No. 6 Men’s Hockey Over Brown", "maxresdefault.jpg", "https://cornellsun.com/article/london-mcdavid-is-making-a-name-for-herself-at-cornell", + null, "11/09", Sport.ICE_HOCKEY )), HighlightData.Video (VideoHighlightData( - "vs Columbia", - "maxresdefault.jpg", - "https://cornellsun.com/article/london-mcdavid-is-making-a-name-for-herself-at-cornell", - "11/9", - Sport.BASEBALL, - GenderDivision.MALE + title = "vs Columbia", + thumbnailImageUrl = "maxresdefault.jpg", + videoUrl = "https://cornellsun.com/article/london-mcdavid-is-making-a-name-for-herself-at-cornell", + date = null, + dateString = "11/9", + sport = Sport.BASEBALL, + gender = GenderDivision.MALE, + duration = "0:44" )) ) \ No newline at end of file