diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b9c1217..af7ba9e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -62,16 +62,15 @@ android { dependencies { // Jetpack Compose dependencies - implementation(platform("androidx.compose:compose-bom:2024.09.00")) - implementation("androidx.compose.material3:material3") - implementation("androidx.compose.ui:ui:1.9.4") - implementation("androidx.compose.ui:ui-tooling-preview:1.4.3") + implementation(platform("androidx.compose:compose-bom:2025.11.01")) + implementation("androidx.compose.ui:ui") + implementation("androidx.compose.ui:ui-tooling-preview") implementation("androidx.activity:activity-compose") implementation("androidx.lifecycle:lifecycle-viewmodel-compose") implementation("androidx.navigation:navigation-compose:2.8.2") implementation(libs.material3) implementation("com.google.dagger:hilt-android:2.51.1") - implementation(libs.androidx.material3) + implementation(libs.androidx.foundation.layout) 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") @@ -80,7 +79,6 @@ dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) implementation(libs.material) - implementation("io.coil-kt:coil-compose:2.0.0") implementation(libs.androidx.activity) implementation(libs.androidx.constraintlayout) implementation(libs.androidx.runtime.android) @@ -88,7 +86,6 @@ dependencies { debugImplementation("androidx.compose.ui:ui-tooling") androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) - implementation(libs.apollo.runtime) implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.1") implementation(libs.apollo.runtime) implementation("io.coil-kt.coil3:coil-compose:3.1.0") 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 new file mode 100644 index 0000000..89b73be --- /dev/null +++ b/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsCardLazyColumn.kt @@ -0,0 +1,88 @@ +package com.cornellappdev.score.components.highlights + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.cornellappdev.score.R +import com.cornellappdev.score.components.EmptyStateBox +import com.cornellappdev.score.components.ScorePreview +import com.cornellappdev.score.model.HighlightData +import com.cornellappdev.score.theme.Style.bodyNormal +import com.cornellappdev.score.util.highlightsList +import com.cornellappdev.score.util.recentSearchList + +@Composable +fun HighlightsCardLazyColumn( + recentSearchList: List, + query: String, + highlightsList: List, + numResultsHeader: (@Composable () -> Unit)? = null +) { + Column( + modifier = Modifier.padding(horizontal = 24.dp) + ) { + if (recentSearchList.isNotEmpty() && query.isEmpty()) { //start state: no search attempted yet + RecentSearches(recentSearchList) + } else if (query.isNotEmpty()) { //filtering - will pull this out to the viewmodel when i do that, just here for sanity check rn + val filteredList = highlightsList.filter { + it.title.contains(query, ignoreCase = true) + } + if (filteredList.isEmpty()) { + EmptyStateBox( + icon = R.drawable.ic_kid_star, + title = "No results yet.", + ) + } else { + numResultsHeader?.invoke() + LazyColumn( + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + items(highlightsList) { item -> + when (item) { + is HighlightData.Video -> VideoHighlightCard(item.data, true) + is HighlightData.Article -> ArticleHighlightCard(item.data, true) + } + } + } + } + } + } +} + +/* Used to display number of results in on HighlightsSearchScreen*/ +@Composable +fun HighlightsCardLazyColumnNumResultsHeader( + size: Int +) { + Column { + Text("$size Results", style = bodyNormal) + Spacer(Modifier.height(16.dp)) + } +} + +@Preview +@Composable +private fun HighlightsCardLazyColumnSubScreenPreview() { + ScorePreview { + HighlightsCardLazyColumn(recentSearchList, "", highlightsList) + } +} + +@Preview +@Composable +private fun HighlightsCardLazyColumnSearchResultsPreview() { + ScorePreview { + HighlightsCardLazyColumn( + recentSearchList, "", highlightsList, + { HighlightsCardLazyColumnNumResultsHeader(highlightsList.size) }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsScreenHeader.kt b/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsScreenHeader.kt deleted file mode 100644 index 65b15c6..0000000 --- a/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsScreenHeader.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.cornellappdev.score.components.highlights - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -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.theme.Style.heading1 -import com.cornellappdev.score.util.sportList - -@Composable -fun HighlightsScreenHeader( - sportList: List, -) { - Column(modifier = Modifier.fillMaxWidth()) { - Text( - modifier = Modifier.padding(start = 24.dp), - style = heading1, - text = "Highlights" - ) - Spacer(modifier = Modifier.height(12.dp)) - Row( - modifier = Modifier - .padding(horizontal = 24.dp) - .clip(shape = RoundedCornerShape(100.dp)) - .clickable(onClick = {/*todo clear the highlight rows and show recent searches*/ }) - ) { - HighlightsSearchBar({}) - } - Spacer(modifier = Modifier.height(16.dp)) - HighlightsFilterRow(sportList, {/*todo on filter selected*/ }) - } -} - -@Preview -@Composable -private fun HighlightsScreenHeaderPreview() { - ScorePreview { - HighlightsScreenHeader(sportList) - } -} \ 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 new file mode 100644 index 0000000..d1bb1e5 --- /dev/null +++ b/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsScreenSearchFilterBar.kt @@ -0,0 +1,97 @@ +package com.cornellappdev.score.components.highlights + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.clickable +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.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +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.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.platform.LocalFocusManager +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.theme.Style.bodyMedium +import com.cornellappdev.score.util.sportList + +@Composable +fun HighlightsScreenSearchFilterBar( + sportList: List +) { + val focusManager = LocalFocusManager.current + var isFocused by remember { mutableStateOf(true) } //todo: should probably be handled by viewModel + Column(modifier = Modifier.fillMaxWidth()) { + Row( + modifier = Modifier + .padding(horizontal = 24.dp) + .clip(shape = RoundedCornerShape(100.dp)) + .onFocusChanged { focusState -> + if (focusState.isFocused && !isFocused) { + /*todo clear the highlight rows and show recent searches*/ + } + }, + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + HighlightsSearchBar( + onSearchClick = { + isFocused = true + }, + modifier = Modifier + .weight(1f) + .onFocusChanged { state -> + // When the search bar loses focus, hide cancel + if (!state.isFocused) { + isFocused = !isFocused + } + } + ) + + AnimatedVisibility( + isFocused + ) { + Text( + "Cancel", + style = bodyMedium, + modifier = Modifier.clickable { + isFocused = true; + focusManager.clearFocus(force = true) + /*todo: clear the text in the search bar*/ + } + ) + } + } + Spacer(modifier = Modifier.height(16.dp)) + HighlightsFilterRow(sportList, {/*todo on filter selected*/ }) + } +} + +@Preview +@Composable +private fun HighlightsScreenSearchFilterBarActivePreview() { + ScorePreview { + HighlightsScreenSearchFilterBar(sportList) + } +} + +@Preview +@Composable +private fun HighlightsScreenSearchFilterBarInactivePreview() { + ScorePreview { + HighlightsScreenSearchFilterBar(sportList) + } +} \ 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 892daa2..3c29f37 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 @@ -1,9 +1,15 @@ package com.cornellappdev.score.components.highlights +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable 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 @@ -15,6 +21,7 @@ import androidx.compose.foundation.text.BasicTextField import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -22,6 +29,9 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.input.VisualTransformation @@ -33,21 +43,25 @@ import com.cornellappdev.score.theme.Style.bodyNormal @Composable fun HighlightsSearchBar( - onSearchClick: () -> Unit + onSearchClick: () -> Unit, + modifier: Modifier = Modifier ) { val interactionSource = remember { MutableInteractionSource() } - var searchQuery by remember { mutableStateOf("") } + var searchQuery by remember { mutableStateOf("") } //todo: to be handled by viewmodel + + val focusRequester = remember { FocusRequester() } + + LaunchedEffect(Unit) { + focusRequester.requestFocus() + } Row( - modifier = Modifier + modifier = modifier .fillMaxWidth() .background(Color.White, RoundedCornerShape(100.dp)) .border(1.dp, GrayLight, RoundedCornerShape(100.dp)) .clip(RoundedCornerShape(100.dp)) - .clickable( - interactionSource = interactionSource, - indication = null - ) { onSearchClick() } + .clickable { onSearchClick() } .padding(horizontal = 16.dp, vertical = 8.dp), verticalAlignment = Alignment.CenterVertically) { @@ -59,6 +73,7 @@ fun HighlightsSearchBar( Spacer(Modifier.width(8.dp)) + var isFocused by remember { mutableStateOf(false) } Box(modifier = Modifier.weight(1f)) { if (searchQuery.isEmpty()) { Text( @@ -69,19 +84,77 @@ fun HighlightsSearchBar( BasicTextField( value = searchQuery, - onValueChange = { searchQuery = it }, + onValueChange = { searchQuery = it /*todo viewmodel load results*/ }, singleLine = true, textStyle = bodyNormal, visualTransformation = VisualTransformation.None, interactionSource = interactionSource, modifier = Modifier + .focusRequester(focusRequester) .fillMaxWidth() .background(Color.Transparent) + .onFocusChanged { focusState -> + if (focusState.isFocused && !isFocused) { + /*todo call viewmodel -> clear highlight rows, load recent search history*/ + isFocused = true + } else if (!focusState.isFocused) { + isFocused = false + } + } + ) + } + + AnimatedVisibility( + visible = searchQuery.isNotEmpty(), + enter = fadeIn() + scaleIn(), + exit = fadeOut() + scaleOut() + ) { + Icon( + painter = painterResource(R.drawable.ic_close), + contentDescription = "clear field", + modifier = Modifier.clickable( + onClick = { searchQuery = "" } + ) ) } } } +/*HighlightsSearchBarUI 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 HighlightsSearchBarUI( + onClick: () -> Unit, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .background(Color.White, RoundedCornerShape(100.dp)) + .border(1.dp, GrayLight, RoundedCornerShape(100.dp)) + .clip(RoundedCornerShape(100.dp)) + .clickable(onClick = { onClick() }) + .padding(horizontal = 16.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Icon( + painter = painterResource(R.drawable.search), + contentDescription = "search icon", + tint = Color.Unspecified + ) + + Text( + text = "Search keywords", + style = bodyNormal.copy(color = Color.Gray) + ) + } +} + +@Preview +@Composable +private fun HighlightsSearchBarUIPreview() { + HighlightsSearchBarUI({}) +} + @Preview @Composable private fun HighlightsSearchBarPreview() { 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 new file mode 100644 index 0000000..1cf3957 --- /dev/null +++ b/app/src/main/java/com/cornellappdev/score/components/highlights/RecentSearches.kt @@ -0,0 +1,91 @@ +package com.cornellappdev.score.components.highlights + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.cornellappdev.score.R +import com.cornellappdev.score.components.ScorePreview +import com.cornellappdev.score.theme.Style.bodyMedium +import com.cornellappdev.score.theme.Style.metricSmallNormal + + +@Composable +private fun RecentSearchItem( + query: String +) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = {/*search the query*/ }) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + Icon( + painter = painterResource(R.drawable.search), + contentDescription = "search icon", + tint = Color.Unspecified + ) + Text(query, style = metricSmallNormal) + } + + IconButton( + onClick = {/*delete this search from the recent searches list*/ }, + modifier = Modifier.size(10.dp) + ) { + Icon( + painter = painterResource(R.drawable.ic_close), + contentDescription = "close icon", + tint = Color.Unspecified + ) + } + } +} + +@Composable +fun RecentSearches( + recentQueriesList: List +) { + Column( + modifier = Modifier + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text("Recent searches", style = bodyMedium) + recentQueriesList.forEach { query -> + RecentSearchItem(query) + } + } +} + +@Preview +@Composable +private fun RecentSearchItemPreview() { + ScorePreview() { + RecentSearchItem("hello") + } +} + +@Preview +@Composable +private fun RecentSearchPreview() { + ScorePreview { + RecentSearches(listOf("Columbia", "Men's ice hockey", "Late goal lifts No.6 men’s hockey")) + } +} diff --git a/app/src/main/java/com/cornellappdev/score/model/Highlights.kt b/app/src/main/java/com/cornellappdev/score/model/Highlights.kt index df339b6..a1a96b2 100644 --- a/app/src/main/java/com/cornellappdev/score/model/Highlights.kt +++ b/app/src/main/java/com/cornellappdev/score/model/Highlights.kt @@ -18,6 +18,13 @@ data class ArticleHighlightData( ) sealed class HighlightData { - data class Video(val data: VideoHighlightData) : HighlightData() - data class Article(val data: ArticleHighlightData) : HighlightData() + abstract val title: String + + data class Video(val data: VideoHighlightData) : HighlightData() { + override val title = data.title + } + + data class Article(val data: ArticleHighlightData) : HighlightData() { + override val title = data.title + } } \ No newline at end of file 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 4818798..3fc8781 100644 --- a/app/src/main/java/com/cornellappdev/score/nav/ScoreNavHost.kt +++ b/app/src/main/java/com/cornellappdev/score/nav/ScoreNavHost.kt @@ -12,6 +12,8 @@ import com.cornellappdev.score.model.ScoreEvent import com.cornellappdev.score.nav.root.ScoreScreens import com.cornellappdev.score.nav.root.ScoreScreens.Home import com.cornellappdev.score.screen.GameDetailsScreen +import com.cornellappdev.score.screen.HighlightsScreen +import com.cornellappdev.score.screen.HighlightsSearchScreen import com.cornellappdev.score.screen.HomeScreen import com.cornellappdev.score.screen.PastGamesScreen import kotlinx.serialization.builtins.ListSerializer @@ -63,7 +65,23 @@ fun ScoreNavHost(navController: NavHostController) { }) } + composable { backStackEntry -> + CompositionLocalProvider(LocalViewModelStoreOwner provides mainScreenViewModelStoreOwner) { + HighlightsScreen(toSearchScreen = { navController.navigate(ScoreScreens.HighlightsSearchScreen) }) + } + } + composable { backStackEntry -> + CompositionLocalProvider(LocalViewModelStoreOwner provides mainScreenViewModelStoreOwner) { + HighlightsSearchScreen( + sportList = emptyList(), + recentSearchList = emptyList(), + highlightsList = emptyList(), + query = "", + header = "" + ) + } + } } } 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 787d3a8..b74f870 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 @@ -109,6 +109,12 @@ sealed class ScoreScreens { @Serializable data class GameScoreSummaryPage(val scoreEvents: String) : ScoreScreens() + + @Serializable + data object HighlightsScreen : ScoreScreens() + + @Serializable + data object HighlightsSearchScreen : ScoreScreens() } fun NavBackStackEntry.toScreen(): ScoreScreens? = @@ -117,6 +123,8 @@ fun NavBackStackEntry.toScreen(): ScoreScreens? = "GameDetailsPage" -> toRoute() "ScoresScreen" -> toRoute() "GameScoreSummaryPage" -> toRoute() + "HighlightsScreen" -> toRoute() + "HighlightsSearchScreen" -> toRoute() else -> throw IllegalArgumentException("Invalid screen") } @@ -134,6 +142,12 @@ val tabs = listOf( selectedIcon = R.drawable.ic_schedule_filled, screen = ScoreScreens.Home, ), + 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 1fb9472..ecad3ec 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/HighlightsScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/HighlightsScreen.kt @@ -5,6 +5,8 @@ import androidx.compose.foundation.layout.Column 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.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -16,18 +18,20 @@ import com.cornellappdev.score.R import com.cornellappdev.score.components.EmptyStateBox import com.cornellappdev.score.components.ScorePreview import com.cornellappdev.score.components.highlights.HighlightsCardRow -import com.cornellappdev.score.components.highlights.HighlightsScreenHeader +import com.cornellappdev.score.components.highlights.HighlightsFilterRow +import com.cornellappdev.score.components.highlights.HighlightsSearchBarUI import com.cornellappdev.score.model.HighlightData import com.cornellappdev.score.model.Sport +import com.cornellappdev.score.theme.Style.heading1 import com.cornellappdev.score.util.highlightsList import com.cornellappdev.score.util.sportList -/*todo: needs a UIState */ @Composable fun HighlightsScreen( - sportList: List, - todayHighlightsList: List, - pastThreeHighlightsList: List + sportList: List = emptyList(), //note - emptyLists are placeholders for nav to work, will replace will viewModel + todayHighlightsList: List = emptyList(), + pastThreeHighlightsList: List = emptyList(), + toSearchScreen: () -> Unit ) { Column( modifier = Modifier @@ -35,11 +39,19 @@ fun HighlightsScreen( .background(color = Color.White) ) { Spacer(modifier = Modifier.height(24.dp)) - HighlightsScreenHeader(sportList) + Column( + modifier = Modifier.padding(horizontal = 24.dp) + ) { + Text("Highlights", style = heading1) + Spacer(modifier = Modifier.height(12.dp)) + HighlightsSearchBarUI(toSearchScreen) + } + Spacer(modifier = Modifier.height(16.dp)) + HighlightsFilterRow(sportList, { /*todo: handle with viewmodel*/ }) Spacer(modifier = Modifier.height(24.dp)) if (todayHighlightsList.isEmpty() && pastThreeHighlightsList.isEmpty()) { EmptyStateBox( - icon = R.drawable.kid_star, + icon = R.drawable.ic_kid_star, title = "No results yet.", ) } @@ -52,14 +64,6 @@ fun HighlightsScreen( } } -@Composable -@Preview -private fun HighlightsScreenHeaderPreview() { - ScorePreview { - HighlightsScreenHeader(sportList) - } -} - data class HighlightsScreenPreviewData( val sportList: List, val todayHighlightList: List, @@ -76,7 +80,7 @@ class HighlightsScreenPreviewProvider : PreviewParameterProvider, + recentSearchList: List, + highlightsList: List, + query: String, + header: String +) { + Column( + modifier = Modifier + .fillMaxSize() + .background(color = Color.White) + .padding(top = 24.dp) + ) { + Row(modifier = Modifier.padding(horizontal = 24.dp)) { + Text(text = header, style = heading2) + } + Spacer(modifier = Modifier.height(16.dp)) + HighlightsScreenSearchFilterBar( + sportList + ) + Spacer(modifier = Modifier.height(24.dp)) + HighlightsCardLazyColumn( + recentSearchList, + query, + highlightsList, + { HighlightsCardLazyColumnNumResultsHeader(highlightsList.size) }) + } +} + +data class HighlightsSearchScreenPreviewData( + 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")) + } +} + +@Preview(showBackground = true) +@Composable +private fun HighlightScreenPreview( + @PreviewParameter(HighlightsSearchScreenPreviewProvider::class) previewData: HighlightsSearchScreenPreviewData +) { + ScorePreview { + HighlightsSearchScreen( + sportList = previewData.sportList, + recentSearchList = previewData.recentSearchList, + highlightsList = highlightsList, + query = previewData.query, + header = "Search All Highlights" + ) + } +} \ 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 new file mode 100644 index 0000000..cfe63e2 --- /dev/null +++ b/app/src/main/java/com/cornellappdev/score/screen/HighlightsSubScreen.kt @@ -0,0 +1,126 @@ +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.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.material3.Icon +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.dropShadow +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.shadow.Shadow +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 com.cornellappdev.score.R +import com.cornellappdev.score.components.EmptyStateBox +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.highlights.RecentSearches +import com.cornellappdev.score.model.HighlightData +import com.cornellappdev.score.model.Sport +import com.cornellappdev.score.theme.Style.heading2 +import com.cornellappdev.score.theme.White +import com.cornellappdev.score.util.highlightsList +import com.cornellappdev.score.util.sportList + +@Composable +private fun HighlightsSubScreenHeader( + header: String, + navigateBack: () -> Unit +) { + Row( + modifier = Modifier + .padding(horizontal = 24.dp, vertical = 12.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(96.dp) + ) { + Icon( + painter = painterResource(R.drawable.ic_left_arrowhead), + contentDescription = "back arrow", + modifier = Modifier.clickable(onClick = { navigateBack() }) + ) + Text(header, style = heading2) + } +} + +@Preview +@Composable +private fun HighlightsSubScreenHeader() { + ScorePreview { + HighlightsSubScreenHeader("Past 3 Days", {}) + } +} + +@Composable +fun HighlightsSubScreen( + sportList: List, + recentSearchList: List, + highlightsList: List, + query: String, + header: String +) { + Column( + modifier = Modifier + .fillMaxSize() + .background(color = Color.White) + .padding(top = 24.dp) + ) { + Surface( + modifier = Modifier + .wrapContentSize() + .dropShadow( + shape = RectangleShape, + shadow = Shadow( + radius = 2.dp, + color = Color.Black.copy(alpha = 0.05f), + offset = DpOffset(0.dp, (2).dp) + ) + ), + color = White + ) { + HighlightsSubScreenHeader(header, {}) + } + Spacer(modifier = Modifier.height(16.dp)) + HighlightsScreenSearchFilterBar( + sportList + ) + Spacer(modifier = Modifier.height(24.dp)) + HighlightsCardLazyColumn( + recentSearchList, + query, + highlightsList + ) + } +} + +@Preview +@Composable +private fun HighlightsSubScreenPreview() { + HighlightsSubScreen( + sportList = sportList, + recentSearchList = listOf( + "Columbia", + "Men's ice hockey", + "Late goal lifts No.6 men’s hockey" + ), + highlightsList = highlightsList, + query = "s", + header = "Past 3 Days" + ) +} \ No newline at end of file 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 6d066e4..476077b 100644 --- a/app/src/main/java/com/cornellappdev/score/util/TestingConstants.kt +++ b/app/src/main/java/com/cornellappdev/score/util/TestingConstants.kt @@ -225,6 +225,8 @@ val sportSelectionList = listOf( SportSelection.SportSelect(Sport.FIELD_HOCKEY) ) +val recentSearchList = listOf("Columbia", "Men's ice hockey", "Late goal lifts No.6 men’s hockey") + //Mixed type val highlightsList = listOf( HighlightData.Video diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml new file mode 100644 index 0000000..af3679e --- /dev/null +++ b/app/src/main/res/drawable/ic_close.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/kid_star.xml b/app/src/main/res/drawable/ic_kid_star.xml similarity index 100% rename from app/src/main/res/drawable/kid_star.xml rename to app/src/main/res/drawable/ic_kid_star.xml diff --git a/app/src/main/res/drawable/ic_nav_star.xml b/app/src/main/res/drawable/ic_nav_star.xml new file mode 100644 index 0000000..05480b9 --- /dev/null +++ b/app/src/main/res/drawable/ic_nav_star.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_nav_star_filled.xml b/app/src/main/res/drawable/ic_nav_star_filled.xml new file mode 100644 index 0000000..693fc9e --- /dev/null +++ b/app/src/main/res/drawable/ic_nav_star_filled.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b53ee5a..c98bfce 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,10 +13,11 @@ constraintlayout = "2.1.4" runtimeAndroid = "1.7.2" apollo = "4.1.1" media3CommonKtx = "1.5.1" -# Using alpha version due to bug with pull to refresh in the latest stabel version -# See https://stackoverflow.com/a/79126321 -material3 = "1.4.0-alpha11" -material3Version = "1.3.2" +material3 = "1.4.0" +material3Version = "1.4.0" +runtime = "1.9.4" +ui = "1.9.4" +foundationLayout = "1.9.4" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -33,6 +34,9 @@ androidx-runtime-android = { group = "androidx.compose.runtime", name = "runtime apollo-runtime = { module = "com.apollographql.apollo:apollo-runtime" } androidx-media3-common-ktx = { group = "androidx.media3", name = "media3-common-ktx", version.ref = "media3CommonKtx" } androidx-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3Version" } +androidx-runtime = { group = "androidx.compose.runtime", name = "runtime", version.ref = "runtime" } +androidx-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "ui" } +androidx-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout", version.ref = "foundationLayout" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" }