diff --git a/app/src/main/java/com/cornellappdev/score/components/EmptyState.kt b/app/src/main/java/com/cornellappdev/score/components/EmptyState.kt index 7e440a5..3b5843f 100644 --- a/app/src/main/java/com/cornellappdev/score/components/EmptyState.kt +++ b/app/src/main/java/com/cornellappdev/score/components/EmptyState.kt @@ -1,5 +1,6 @@ package com.cornellappdev.score.components +import androidx.annotation.DrawableRes import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -7,10 +8,12 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp @@ -21,58 +24,46 @@ import com.cornellappdev.score.theme.GrayPrimary import com.cornellappdev.score.theme.Style.bodyNormal import com.cornellappdev.score.theme.Style.heading2 -@Composable -fun EmptyState( - modifier: Modifier = Modifier, - title: String = "No games yet.", - subtitle: String = "Check back here later!" -) { - Column( - modifier = modifier, - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center - ) { - Image( - painter = painterResource(R.drawable.ic_speaker_gray), - contentDescription = "score speaker icon" - ) - Spacer(modifier = Modifier.height(16.dp)) - Text( - text = title, - style = heading2.copy(color = GrayPrimary) - ) - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = subtitle, - style = bodyNormal.copy(color = GrayMedium) - ) - } -} - @Composable fun EmptyStateBox( + @DrawableRes icon: Int, + title: String, modifier: Modifier = Modifier, - height: Dp = 550.dp, - title: String = "No games yet.", - subtitle: String = "Check back here later!" + subtitle: String = "Check back here later!", + height: Dp = 550.dp //(appx height when full-screen error) ) { Box( modifier = modifier .height(height) .fillMaxWidth(), contentAlignment = Alignment.Center ) { - EmptyState(title = title, subtitle = subtitle) + Column( + modifier = Modifier, + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Icon( + painter = painterResource(icon), + contentDescription = "empty state icon", + tint = Color.Unspecified + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = title, + style = heading2.copy(color = GrayPrimary) + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = subtitle, + style = bodyNormal.copy(color = GrayMedium) + ) + } } } -@Preview -@Composable -private fun EmptyStatePreview() = ScorePreview { - EmptyState() -} @Preview @Composable private fun EmptyStateBoxPreview() = ScorePreview { - EmptyStateBox() -} \ No newline at end of file + EmptyStateBox(R.drawable.ic_speaker_gray, "No games yet.") +} diff --git a/app/src/main/java/com/cornellappdev/score/components/SportSelectorHeader.kt b/app/src/main/java/com/cornellappdev/score/components/SportSelectorHeader.kt index 313ccd9..fd09f9a 100644 --- a/app/src/main/java/com/cornellappdev/score/components/SportSelectorHeader.kt +++ b/app/src/main/java/com/cornellappdev/score/components/SportSelectorHeader.kt @@ -78,7 +78,10 @@ fun SportSelectorHeader( } } Spacer(Modifier.height(24.dp)) - LazyRow(Modifier.fillMaxWidth()) { + LazyRow( + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(20.dp) + ) { items(sports) { selection -> when (selection) { SportSelection.All -> { @@ -108,11 +111,7 @@ fun SportSelectorHeader( } } } - - - Spacer(modifier = Modifier.width(20.dp)) } - item { Spacer(modifier = Modifier.width(4.dp)) } } } } 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 new file mode 100644 index 0000000..bc34227 --- /dev/null +++ b/app/src/main/java/com/cornellappdev/score/components/highlights/ArticleHighlightsCard.kt @@ -0,0 +1,133 @@ +package com.cornellappdev.score.components.highlights + +import androidx.compose.foundation.background +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.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.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicText +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.LinkAnnotation +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextLinkStyles +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.text.withLink +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import coil3.compose.AsyncImage +import com.cornellappdev.score.R +import com.cornellappdev.score.model.ArticleHighlightData +import com.cornellappdev.score.model.Sport +import com.cornellappdev.score.theme.Style.bodySemibold +import com.cornellappdev.score.theme.Style.heading2 +import com.cornellappdev.score.theme.Style.labelsNormal +import com.cornellappdev.score.theme.White + +@Composable +fun ArticleHighlightCard( + articleHighlight: ArticleHighlightData, + isWideFormat: Boolean +) { + Box( + modifier = Modifier + .then(if (isWideFormat) Modifier.fillMaxWidth() else Modifier.width(241.dp)) + .height(192.dp) + .clip(shape = RoundedCornerShape(12.dp)) + ) { + //todo: empty state if image doesn't load + AsyncImage( + model = articleHighlight.imageUrl, + contentDescription = "highlight image", + contentScale = ContentScale.Crop + ) + Box( + modifier = Modifier + .fillMaxSize() + .background(color = Color.Black.copy(alpha = 0.4f)) + ) + Column( + modifier = Modifier + .fillMaxSize() + .padding(12.dp), + verticalArrangement = Arrangement.SpaceBetween + ) { + Text( + style = heading2, + color = Color.White, + text = articleHighlight.title, + maxLines = 4, + overflow = TextOverflow.Ellipsis + ) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + if (isWideFormat) { + Text("Read at ", color = White) + } + ExternalLink( + articleHighlight.articleUrl, + urlLabel = "Cornell Daily Sun", + linkColor = White + ) + } + Text( + color = Color.White, + style = labelsNormal, + text = articleHighlight.date + ) + } + } + } +} + +@Preview +@Composable +private fun ArticleHighlightCardPreview() { + ArticleHighlightCard( + 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", + "11/9", + Sport.ICE_HOCKEY + ), + false + ) +} + +@Preview +@Composable +private fun WideArticleHighlightCardPreview() { + ArticleHighlightCard( + 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", + "11/9", + Sport.ICE_HOCKEY + ), + true + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/score/components/highlights/ExternalLink.kt b/app/src/main/java/com/cornellappdev/score/components/highlights/ExternalLink.kt new file mode 100644 index 0000000..957400f --- /dev/null +++ b/app/src/main/java/com/cornellappdev/score/components/highlights/ExternalLink.kt @@ -0,0 +1,55 @@ +package com.cornellappdev.score.components.highlights + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.text.BasicText +import androidx.compose.material3.Icon +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.text.LinkAnnotation +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextLinkStyles +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.withLink +import com.cornellappdev.score.R + +@Composable +fun ExternalLink( + url: String, + urlLabel: String, + linkColor: Color, +) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + BasicText( + text = buildAnnotatedString { + withLink( + LinkAnnotation.Url( + url, + TextLinkStyles( + style = SpanStyle( + textDecoration = TextDecoration.Underline, + color = linkColor, + fontWeight = FontWeight.Bold + ) + ), + ) + ) { + append(urlLabel) + } + } + ) + Icon( + painter = painterResource(R.drawable.arrow_outward), + contentDescription = "external link arrow", + tint = linkColor + ) + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..57755f6 --- /dev/null +++ b/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsCardRow.kt @@ -0,0 +1,87 @@ +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.Spacer +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.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.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.model.HighlightData +import com.cornellappdev.score.theme.Style.bodyNormal +import com.cornellappdev.score.theme.Style.heading2 +import com.cornellappdev.score.util.highlightsList + +@Composable +fun HighlightsCardRow( + highlightsList: List, + rowHeader: String +) { + Column( + modifier = Modifier.fillMaxWidth() + ) { + Row( + modifier = Modifier + .padding(horizontal = 24.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = rowHeader, + style = heading2 + ) + Row( + modifier = Modifier.clickable {/*todo navigation to Today screen*/ }, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "${highlightsList.size} Results", + style = bodyNormal + ) + Icon( + painter = painterResource(R.drawable.ic_right_chevron_small), + contentDescription = "right chevron", + tint = Color.Unspecified + ) + } + } + Spacer(modifier = Modifier.height(16.dp)) + LazyRow( + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + item { Spacer(Modifier.width(8.dp)) } + items(highlightsList) { item -> + when (item) { + is HighlightData.Video -> VideoHighlightCard(item.data, false) + is HighlightData.Article -> ArticleHighlightCard(item.data, false) + } + } + item { Spacer(Modifier.width(8.dp)) } + } + Spacer(modifier = Modifier.height(24.dp)) + } +} + +@Preview +@Composable +private fun HighlightsCardRowPreview() { + ScorePreview { + 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 new file mode 100644 index 0000000..ea273ff --- /dev/null +++ b/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsFilter.kt @@ -0,0 +1,98 @@ +package com.cornellappdev.score.components.highlights + + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ButtonDefaults.outlinedButtonColors +import androidx.compose.material3.Icon +import androidx.compose.material3.OutlinedButton +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.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.model.Sport +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 + +@Composable +private fun HighlightsFilterButton( + sport: Sport, + onFilterSelected: (Sport) -> Unit, + isSelected: Boolean = false, +) { + OutlinedButton( + modifier = Modifier + .height(32.dp), + border = BorderStroke(width = 1.dp, color = Stroke), + onClick = { onFilterSelected(sport) }, + shape = RoundedCornerShape(100.dp), + colors = outlinedButtonColors( + containerColor = if (isSelected) GrayLight else White, + contentColor = GrayPrimary + ), + contentPadding = PaddingValues(horizontal = 12.dp, vertical = 4.dp), + ) { + Icon( + painter = painterResource(sport.emptyIcon), + contentDescription = "sport Icon", + tint = Color.Unspecified + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = sport.displayName, + style = bodyNormal + ) + } +} + +@Composable +fun HighlightsFilterRow( + sportList: List, + onFilterSelected: (Sport) -> Unit, +) { + LazyRow( + modifier = Modifier + .fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + item { Spacer(Modifier.width(12.dp)) } + items(sportList) { item -> + HighlightsFilterButton(item, onFilterSelected) + } + item { Spacer(Modifier.width(12.dp)) } + } +} + +@Preview +@Composable +private fun HighlightsFilterButtonPreview() { + var isSelected by remember { mutableStateOf(false) } + HighlightsFilterButton(Sport.BASEBALL, { isSelected = !isSelected }, isSelected = isSelected) +} + +@Preview +@Composable +private fun HighlightsFilterRowPreview() { + HighlightsFilterRow(sportList, {}) +} 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 new file mode 100644 index 0000000..65b15c6 --- /dev/null +++ b/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsScreenHeader.kt @@ -0,0 +1,52 @@ +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/HighlightsSearchBar.kt b/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsSearchBar.kt new file mode 100644 index 0000000..892daa2 --- /dev/null +++ b/app/src/main/java/com/cornellappdev/score/components/highlights/HighlightsSearchBar.kt @@ -0,0 +1,89 @@ +package com.cornellappdev.score.components.highlights + +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.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.material3.Icon +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.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.cornellappdev.score.R +import com.cornellappdev.score.theme.GrayLight +import com.cornellappdev.score.theme.Style.bodyNormal + +@Composable +fun HighlightsSearchBar( + onSearchClick: () -> Unit +) { + val interactionSource = remember { MutableInteractionSource() } + var searchQuery by remember { mutableStateOf("") } + + Row( + 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() } + .padding(horizontal = 16.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically) { + + Icon( + painter = painterResource(R.drawable.search), + contentDescription = "search icon", + tint = Color.Unspecified + ) + + Spacer(Modifier.width(8.dp)) + + Box(modifier = Modifier.weight(1f)) { + if (searchQuery.isEmpty()) { + Text( + text = "Search keywords", + style = bodyNormal.copy(color = Color.Gray) + ) + } + + BasicTextField( + value = searchQuery, + onValueChange = { searchQuery = it }, + singleLine = true, + textStyle = bodyNormal, + visualTransformation = VisualTransformation.None, + interactionSource = interactionSource, + modifier = Modifier + .fillMaxWidth() + .background(Color.Transparent) + ) + } + } +} + +@Preview +@Composable +private fun HighlightsSearchBarPreview() { + HighlightsSearchBar({}) +} \ No newline at end of file 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 new file mode 100644 index 0000000..44c4efb --- /dev/null +++ b/app/src/main/java/com/cornellappdev/score/components/highlights/VideoHighlightsCard.kt @@ -0,0 +1,191 @@ +package com.cornellappdev.score.components.highlights + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +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.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextOverflow +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 coil3.compose.AsyncImage +import com.cornellappdev.score.R +import com.cornellappdev.score.components.ScorePreview +import com.cornellappdev.score.model.GenderDivision +import com.cornellappdev.score.model.Sport +import com.cornellappdev.score.model.VideoHighlightData +import com.cornellappdev.score.theme.CrimsonPrimary +import com.cornellappdev.score.theme.GrayStroke +import com.cornellappdev.score.theme.Style.heading2 +import com.cornellappdev.score.theme.Style.labelsNormal + +@Composable +private fun VideoHighlightCardHeader( + imageUrl: String +) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(117.dp) + ) { + //todo: empty state if image doesn't load + AsyncImage( + model = imageUrl, + contentDescription = "Highlight article image", + contentScale = ContentScale.Crop + ) + Box( + modifier = Modifier + .fillMaxSize() + .background(color = Color.Black.copy(alpha = 0.4f)) + ) + } +} + +@Composable +fun VideoHighlightCardBody( + videoHighlight: VideoHighlightData, + isWideFormat: Boolean +) { + Column( + modifier = Modifier + .fillMaxWidth() + .height(75.dp) + .background(color = Color.White) + .border( + width = 1.dp, + color = GrayStroke, + shape = RoundedCornerShape(bottomStart = 12.dp, bottomEnd = 12.dp) + ) + .padding(horizontal = 16.dp, vertical = 8.dp), + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + modifier = Modifier.weight(1f), + style = heading2, + text = videoHighlight.title, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + 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 + ) + } + } + Spacer(Modifier.height(8.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + if (isWideFormat) { + Text("Watch on ") + } + ExternalLink(videoHighlight.videoUrl, "Youtube", CrimsonPrimary) + } + Text( + style = labelsNormal, + text = videoHighlight.date + ) + } + } +} + +@Composable +fun VideoHighlightCard( + videoHighlight: VideoHighlightData, + isWideFormat: Boolean, + modifier: Modifier = Modifier +) { + Column( + modifier = modifier + .then(if (isWideFormat) Modifier.fillMaxWidth() else Modifier.width(241.dp)) + .clip(RoundedCornerShape(12.dp)) + ) { + VideoHighlightCardHeader(videoHighlight.thumbnailImageUrl) + VideoHighlightCardBody(videoHighlight, isWideFormat) + } +} + +data class VideoHighlightPreviewData( + val videoHighlight: VideoHighlightData, + val isWideFormat: Boolean +) + +class VideoHighlightsPreviewProvider : PreviewParameterProvider { + override val values: Sequence = 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 + ), + 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 + ) + ) + for (sample in samples) { + yield(VideoHighlightPreviewData(sample, true)) + yield(VideoHighlightPreviewData(sample, false)) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun VideoHighlightCardPreview( + @PreviewParameter(VideoHighlightsPreviewProvider::class) previewData: VideoHighlightPreviewData +) { + ScorePreview { + VideoHighlightCard( + videoHighlight = previewData.videoHighlight, + isWideFormat = previewData.isWideFormat + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/score/model/Highlights.kt b/app/src/main/java/com/cornellappdev/score/model/Highlights.kt new file mode 100644 index 0000000..df339b6 --- /dev/null +++ b/app/src/main/java/com/cornellappdev/score/model/Highlights.kt @@ -0,0 +1,23 @@ +package com.cornellappdev.score.model + +data class VideoHighlightData( + val title: String, + val thumbnailImageUrl: String, + val videoUrl: String, + val date: String, + val sport: Sport, + val gender: GenderDivision +) + +data class ArticleHighlightData( + val title: String, + val imageUrl: String, + val articleUrl: String, + val date: String, + val sport: Sport +) + +sealed class HighlightData { + data class Video(val data: VideoHighlightData) : HighlightData() + data class Article(val data: ArticleHighlightData) : HighlightData() +} \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/score/screen/GameDetailsScreen.kt b/app/src/main/java/com/cornellappdev/score/screen/GameDetailsScreen.kt index 4062de6..001f500 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/GameDetailsScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/GameDetailsScreen.kt @@ -198,7 +198,11 @@ fun GameDetailsContent( Spacer(modifier = Modifier.height(16.dp)) ScoringSummary(gameCard.scoreEvent) } else { - EmptyStateBox(height = 200.dp, title = "No scores yet.") + EmptyStateBox( + icon = R.drawable.ic_speaker_gray, + title = "No scores yet.", + height = 200.dp + ) } } else { val context = LocalContext.current @@ -331,3 +335,54 @@ private fun GameDetailsPreview() { ), navigateToGameScoreSummary = {} ) } + +@Preview +@Composable +private fun EmptyGameDetailsPreview() { + GameDetailsContent( + DetailsCardData( + title = "Championship Game", + opponentLogo = "https://example.com/logo.png", + opponent = "Wildcats", + opponentColor = Color(0xFF123456), + date = LocalDate.of(2025, 4, 20), + time = "7:30 PM", + dateString = "April 20, 2025", + isPastStartTime = true, + location = "Main Stadium", + locationString = "Main Stadium, Cityville", + gender = "Men's", + genderIcon = 123, // Dummy resource ID + sport = "Basketball", + sportIcon = 456, // Dummy resource ID + boxScore = emptyList(), + scoreBreakdown = listOf( + emptyList(), + emptyList() + ), + gameData = GameData( + Pair( + TeamScore( + team = TeamBoxScore( + name = "Tigers", + ), + scoresByPeriod = emptyList(), + totalScore = 0 + ), + TeamScore( + team = TeamBoxScore( + name = "Wildcats", + ), + scoresByPeriod = emptyList(), + totalScore = 0 + ) + ) + ), + scoreEvent = emptyList(), + daysUntilGame = 0, + hoursUntilGame = 0, + homeScore = 0, + oppScore = 0 + ), navigateToGameScoreSummary = {} + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/cornellappdev/score/screen/HighlightsScreen.kt b/app/src/main/java/com/cornellappdev/score/screen/HighlightsScreen.kt new file mode 100644 index 0000000..1fb9472 --- /dev/null +++ b/app/src/main/java/com/cornellappdev/score/screen/HighlightsScreen.kt @@ -0,0 +1,89 @@ +package com.cornellappdev.score.screen + +import androidx.compose.foundation.background +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.runtime.Composable +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 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.model.HighlightData +import com.cornellappdev.score.model.Sport +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 +) { + Column( + modifier = Modifier + .fillMaxSize() + .background(color = Color.White) + ) { + Spacer(modifier = Modifier.height(24.dp)) + HighlightsScreenHeader(sportList) + Spacer(modifier = Modifier.height(24.dp)) + if (todayHighlightsList.isEmpty() && pastThreeHighlightsList.isEmpty()) { + EmptyStateBox( + icon = R.drawable.kid_star, + title = "No results yet.", + ) + } + if (todayHighlightsList.isNotEmpty()) { + HighlightsCardRow(todayHighlightsList, "Today") + } + if (pastThreeHighlightsList.isNotEmpty()) { + HighlightsCardRow(pastThreeHighlightsList, "Past 3 days") + } + } +} + +@Composable +@Preview +private fun HighlightsScreenHeaderPreview() { + ScorePreview { + HighlightsScreenHeader(sportList) + } +} + +data class HighlightsScreenPreviewData( + 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)) + } +} + +@Preview(showBackground = true) +@Composable +private fun VideoHighlightCardPreview( + @PreviewParameter(HighlightsScreenPreviewProvider::class) previewData: HighlightsScreenPreviewData +) { + ScorePreview { + HighlightsScreen( + sportList = previewData.sportList, + todayHighlightsList = previewData.todayHighlightList, + pastThreeHighlightsList = previewData.pastHighlightList, + ) + } +} \ No newline at end of file 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 9e42a06..3145dfc 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/HomeScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/HomeScreen.kt @@ -24,6 +24,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview 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.GameCard @@ -175,7 +176,7 @@ private fun HomeLazyColumn( } } else { item { - EmptyStateBox() + EmptyStateBox(icon = R.drawable.ic_speaker_gray, title = "No games yet.") } } } diff --git a/app/src/main/java/com/cornellappdev/score/screen/PastGamesScreen.kt b/app/src/main/java/com/cornellappdev/score/screen/PastGamesScreen.kt index ce4014f..8cd7f11 100644 --- a/app/src/main/java/com/cornellappdev/score/screen/PastGamesScreen.kt +++ b/app/src/main/java/com/cornellappdev/score/screen/PastGamesScreen.kt @@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues 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 @@ -22,7 +21,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel -import com.cornellappdev.score.components.EmptyState +import com.cornellappdev.score.R import com.cornellappdev.score.components.EmptyStateBox import com.cornellappdev.score.components.ErrorState import com.cornellappdev.score.components.GamesCarousel @@ -167,7 +166,7 @@ private fun PastGamesLazyColumn( } } else { item { - EmptyStateBox() + EmptyStateBox(icon = R.drawable.ic_speaker_gray, title = "No games yet.") } } } diff --git a/app/src/main/java/com/cornellappdev/score/theme/TextStyle.kt b/app/src/main/java/com/cornellappdev/score/theme/TextStyle.kt index c2fd46e..c01fafa 100644 --- a/app/src/main/java/com/cornellappdev/score/theme/TextStyle.kt +++ b/app/src/main/java/com/cornellappdev/score/theme/TextStyle.kt @@ -8,7 +8,6 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp object Style { @@ -25,11 +24,11 @@ object Style { fontWeight = FontWeight(700), fontStyle = FontStyle.Italic, color = Color.White, - shadow = Shadow( - color = Color(0f, 0f, 0f, 0.4f), - offset = Offset(0f, 0f), - blurRadius = 4f - ) + shadow = Shadow( + color = Color(0f, 0f, 0f, 0.4f), + offset = Offset(0f, 0f), + blurRadius = 4f + ) ) val universityText = TextStyle( @@ -154,6 +153,13 @@ object Style { fontWeight = FontWeight(400) ) + val bodyNormalGray = TextStyle( + fontSize = 14.sp, + fontFamily = poppinsFamily, + fontWeight = FontWeight(400), + color = GrayMedium + ) + val spanBodyNormal = SpanStyle( fontSize = 14.sp, fontFamily = poppinsFamily, 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 6f5cd35..6d066e4 100644 --- a/app/src/main/java/com/cornellappdev/score/util/TestingConstants.kt +++ b/app/src/main/java/com/cornellappdev/score/util/TestingConstants.kt @@ -2,8 +2,12 @@ package com.cornellappdev.score.util import androidx.compose.ui.graphics.Color import com.cornellappdev.score.R +import com.cornellappdev.score.model.ArticleHighlightData import com.cornellappdev.score.model.GameCardData import com.cornellappdev.score.model.GameData +import com.cornellappdev.score.model.GenderDivision +import com.cornellappdev.score.model.HighlightData +import com.cornellappdev.score.model.VideoHighlightData import com.cornellappdev.score.model.ScoreEvent import com.cornellappdev.score.model.Sport import com.cornellappdev.score.model.SportSelection @@ -216,4 +220,54 @@ val sportSelectionList = listOf( SportSelection.SportSelect(Sport.BASEBALL), SportSelection.SportSelect(Sport.BASKETBALL), SportSelection.SportSelect(Sport.CROSS_COUNTRY), + SportSelection.SportSelect(Sport.EQUESTRIAN), + SportSelection.SportSelect(Sport.FENCING), + SportSelection.SportSelect(Sport.FIELD_HOCKEY) ) + +//Mixed type +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 + )), + 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", + "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 + )), + 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", + "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 + )) +) \ No newline at end of file diff --git a/app/src/main/res/drawable/arrow_outward.xml b/app/src/main/res/drawable/arrow_outward.xml new file mode 100644 index 0000000..c55592c --- /dev/null +++ b/app/src/main/res/drawable/arrow_outward.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_right_chevron_small.xml b/app/src/main/res/drawable/ic_right_chevron_small.xml new file mode 100644 index 0000000..e2190fe --- /dev/null +++ b/app/src/main/res/drawable/ic_right_chevron_small.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/app/src/main/res/drawable/kid_star.xml b/app/src/main/res/drawable/kid_star.xml new file mode 100644 index 0000000..9317f11 --- /dev/null +++ b/app/src/main/res/drawable/kid_star.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/app/src/main/res/drawable/search.xml b/app/src/main/res/drawable/search.xml new file mode 100644 index 0000000..3243b22 --- /dev/null +++ b/app/src/main/res/drawable/search.xml @@ -0,0 +1,13 @@ + + + + + +