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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package com.cornellappdev.score.components.highlights

import android.graphics.BitmapFactory
import androidx.compose.foundation.horizontalScroll
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.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import coil3.annotation.ExperimentalCoilApi
import coil3.asImage
import coil3.compose.AsyncImagePreviewHandler
import coil3.compose.LocalAsyncImagePreviewHandler
import com.cornellappdev.score.R
import com.cornellappdev.score.components.ScorePreview
import com.cornellappdev.score.model.ArticleHighlightData
import com.cornellappdev.score.model.GenderDivision
import com.cornellappdev.score.model.Sport
import com.cornellappdev.score.model.VideoHighlightData
import com.cornellappdev.score.theme.Style.heading1

@OptIn(ExperimentalCoilApi::class)
@Preview(showBackground = true, widthDp = 393)
@Composable
internal fun HighlightFavoritingPreview() {
val context = LocalContext.current
val videoImage = "android.resource://${context.packageName}/${R.drawable.preview_highlight_soccer}"
val articleImage = "android.resource://${context.packageName}/${R.drawable.preview_highlight_hockey}"
val previewHandler = AsyncImagePreviewHandler { request ->
BitmapFactory.decodeResource(
context.resources,
if (request.data == videoImage) R.drawable.preview_highlight_soccer
else R.drawable.preview_highlight_hockey
).asImage()
}
CompositionLocalProvider(LocalAsyncImagePreviewHandler provides previewHandler) {
ScorePreview {
Column(Modifier.padding(vertical = 24.dp)) {
Text("Highlights", style = heading1, modifier = Modifier.padding(horizontal = 24.dp))
Spacer(Modifier.height(24.dp))
Row(
modifier = Modifier.horizontalScroll(rememberScrollState()).padding(horizontal = 24.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
VideoHighlightCard(
videoHighlight = VideoHighlightData(
title = "Highlights: Cornell Women’s Soccer",
thumbnailImageUrl = videoImage,
videoUrl = "https://www.youtube.com/",
date = "10/15",
sport = Sport.SOCCER,
gender = GenderDivision.FEMALE
),
isWideFormat = false
)
ArticleHighlightCard(
articleHighlight = ArticleHighlightData(
title = "CORNELL NOTES | No. 14 Women’s Hockey Readies for Another Season",
imageUrl = articleImage,
articleUrl = "https://cornellsun.com/",
date = "10/15",
sport = Sport.ICE_HOCKEY
),
isWideFormat = false
)
}
}
}
}
}

@Preview(showBackground = true, widthDp = 393)
@Composable
private fun HighlightSignInPromptPreview() {
ScorePreview {
HighlightSignInSheetContent(onDismiss = {})
}
}

@Preview(showBackground = true)
@Composable
private fun HighlightBookmarkStatesPreview() {
Row {
HighlightFavoriteButton(highlightKey = "signed-out")
HighlightFavoriteButton(highlightKey = "signed-in", hasAccount = true)
HighlightFavoriteButton(highlightKey = "saved", hasAccount = true, initiallyFavorited = true)
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,36 @@ 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.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
isWideFormat: Boolean,
hasAccount: Boolean = false
) {
Box(
modifier = Modifier
Expand All @@ -54,6 +46,7 @@ fun ArticleHighlightCard(
AsyncImage(
model = articleHighlight.imageUrl,
contentDescription = "highlight image",
modifier = Modifier.matchParentSize(),
contentScale = ContentScale.Crop
)
Box(
Expand All @@ -67,13 +60,20 @@ fun ArticleHighlightCard(
.padding(12.dp),
verticalArrangement = Arrangement.SpaceBetween
) {
Text(
style = heading2,
color = Color.White,
text = articleHighlight.title,
maxLines = 4,
overflow = TextOverflow.Ellipsis
)
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.Top
) {
Text(
modifier = Modifier.weight(1f),
style = heading2,
color = Color.White,
text = articleHighlight.title,
maxLines = 4,
overflow = TextOverflow.Ellipsis
)
Spacer(Modifier.size(40.dp, 32.dp))
}

Row(
modifier = Modifier.fillMaxWidth(),
Expand All @@ -99,6 +99,11 @@ fun ArticleHighlightCard(
)
}
}
HighlightFavoriteButton(
highlightKey = articleHighlight.articleUrl,
hasAccount = hasAccount,
modifier = Modifier.align(Alignment.TopEnd).padding(8.dp)
)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.cornellappdev.score.components.highlights

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.semantics.selected
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import com.cornellappdev.score.R
import com.cornellappdev.score.theme.CrimsonPrimary
import com.cornellappdev.score.theme.White

/** UI-only state until account sign-in and saved highlights are available. */
@Composable
fun HighlightFavoriteButton(
highlightKey: String,
modifier: Modifier = Modifier,
hasAccount: Boolean = false,
initiallyFavorited: Boolean = false
) {
var isFavorited by rememberSaveable(highlightKey) { mutableStateOf(initiallyFavorited) }
var showSignIn by rememberSaveable(highlightKey) { mutableStateOf(false) }

// Keep a 48dp touch target around the design's 32dp button.
Box(modifier = modifier.size(48.dp), contentAlignment = Alignment.Center) {
Surface(
onClick = {
if (hasAccount) {
isFavorited = !isFavorited
} else {
showSignIn = true
}
},
modifier = Modifier.size(32.dp).semantics { selected = isFavorited },
shape = RoundedCornerShape(8.dp),
color = if (isFavorited) CrimsonPrimary else White,
shadowElevation = 3.dp
) {
Icon(
painter = painterResource(R.drawable.ic_highlight_bookmark),
contentDescription = if (isFavorited) "Unfavorite highlight" else "Favorite highlight",
tint = if (isFavorited) White else CrimsonPrimary,
modifier = Modifier.padding(8.dp)
)
}
}

if (showSignIn) {
HighlightSignInSheet(onDismiss = { showSignIn = false })
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package com.cornellappdev.score.components.highlights

import androidx.compose.foundation.BorderStroke
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.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.rememberModalBottomSheetState
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.unit.dp
import com.cornellappdev.score.R
import com.cornellappdev.score.components.ButtonPrimary
import com.cornellappdev.score.theme.GrayLight
import com.cornellappdev.score.theme.GrayMedium
import com.cornellappdev.score.theme.GrayPrimary
import com.cornellappdev.score.theme.GrayStroke
import com.cornellappdev.score.theme.Style.bodyMedium
import com.cornellappdev.score.theme.Style.heading1
import com.cornellappdev.score.theme.Style.labelsNormal
import com.cornellappdev.score.theme.White

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HighlightSignInSheet(onDismiss: () -> Unit) {
ModalBottomSheet(
onDismissRequest = onDismiss,
sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
shape = RoundedCornerShape(topStart = 32.dp, topEnd = 32.dp),
containerColor = White,
scrimColor = Color.Transparent,
dragHandle = null
) {
HighlightSignInSheetContent(onDismiss = onDismiss)
}
}

@Composable
internal fun HighlightSignInSheetContent(
onDismiss: () -> Unit,
// Account navigation will be connected once the settings/sign-in work is merged.
onSignIn: () -> Unit = {},
onCreateAccount: () -> Unit = {}
) {
Box(
Modifier
.fillMaxWidth()
.heightIn(min = 398.dp)
.border(1.dp, GrayStroke, RoundedCornerShape(topStart = 32.dp, topEnd = 32.dp))
) {
Surface(
modifier = Modifier.align(Alignment.TopCenter).padding(top = 15.dp).size(32.dp, 4.dp),
shape = RoundedCornerShape(4.dp),
color = GrayLight
) {}
Column(
modifier = Modifier.fillMaxWidth().padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Box(Modifier.fillMaxWidth().height(24.dp), contentAlignment = Alignment.CenterEnd) {
IconButton(onClick = onDismiss, modifier = Modifier.size(24.dp)) {
Icon(
painter = painterResource(R.drawable.ic_close),
contentDescription = "Close sign in prompt",
tint = GrayMedium
)
}
}
Spacer(Modifier.height(8.dp))
Text("Sign In to Save", style = heading1, color = GrayPrimary)
Spacer(Modifier.height(32.dp))
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
ButtonPrimary(
text = "Sign In",
icon = null,
modifier = Modifier.fillMaxWidth().height(48.dp),
onClick = onSignIn
)
OutlinedButton(
onClick = onCreateAccount,
modifier = Modifier.fillMaxWidth().height(48.dp),
shape = RoundedCornerShape(100.dp),
border = BorderStroke(1.dp, GrayLight),
colors = ButtonDefaults.outlinedButtonColors(contentColor = GrayPrimary)
) {
Text("Create an Account", style = bodyMedium)
}
}
Spacer(Modifier.height(8.dp))
TextButton(onClick = onDismiss) {
Text("Not now", style = labelsNormal, color = GrayMedium)
}
Spacer(Modifier.height(24.dp))
}
}
}
Loading
Loading