From de64293c1e72d673bbe54f19b4aa0eb9d7cbb694 Mon Sep 17 00:00:00 2001 From: ashiagr Date: Tue, 15 Nov 2022 12:13:44 +0530 Subject: [PATCH 1/2] Add listened categories story design --- .../endofyear/components/CategoryPillar.kt | 167 ++++++++++++++++ .../utils/StoryModifierExtensions.kt | 25 ++- .../stories/StoryTopListenedCategoriesView.kt | 182 +++++++++--------- .../src/main/res/drawable/rectangle.xml | 9 + .../compose/components/TextStyles.kt | 35 +++- 5 files changed, 306 insertions(+), 112 deletions(-) create mode 100644 modules/features/endofyear/src/main/java/au/com/shiftyjelly/pocketcasts/endofyear/components/CategoryPillar.kt create mode 100644 modules/features/endofyear/src/main/res/drawable/rectangle.xml diff --git a/modules/features/endofyear/src/main/java/au/com/shiftyjelly/pocketcasts/endofyear/components/CategoryPillar.kt b/modules/features/endofyear/src/main/java/au/com/shiftyjelly/pocketcasts/endofyear/components/CategoryPillar.kt new file mode 100644 index 00000000000..9ebe3af0c53 --- /dev/null +++ b/modules/features/endofyear/src/main/java/au/com/shiftyjelly/pocketcasts/endofyear/components/CategoryPillar.kt @@ -0,0 +1,167 @@ +package au.com.shiftyjelly.pocketcasts.endofyear.components + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Matrix +import androidx.compose.ui.graphics.drawscope.withTransform +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.painterResource +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.dp +import androidx.compose.ui.unit.sp +import au.com.shiftyjelly.pocketcasts.compose.components.TextH30 +import au.com.shiftyjelly.pocketcasts.compose.components.TextH40 +import au.com.shiftyjelly.pocketcasts.compose.components.TextH70 +import au.com.shiftyjelly.pocketcasts.endofyear.R +import au.com.shiftyjelly.pocketcasts.endofyear.utils.FadeDirection +import au.com.shiftyjelly.pocketcasts.endofyear.utils.dynamicBackground +import au.com.shiftyjelly.pocketcasts.utils.extensions.pxToDp + +private const val PillarRotationAngle = -30f +private const val PillarTextSkew = 0.5f +private const val PillarTopAspectRatio = .58f +private val PillarColor = Color(0xFFFE7E61) + +@Composable +fun CategoryPillar( + title: String, + duration: String, + text: String, + height: Dp, + modifier: Modifier = Modifier, +) { + val context = LocalContext.current + val currentLocalView = LocalView.current + val width = currentLocalView.width.pxToDp(context).dp * .2f + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Bottom, + modifier = modifier + ) { + Title( + text = title, + modifier = Modifier + .padding(bottom = 0.dp) + .width(width) + ) + Duration( + text = duration, + modifier = Modifier + .width(width) + .alpha(0.8f) + .padding(bottom = 30.dp) + ) + Pillar( + text = text, + width = width, + height = height, + ) + } +} + +@Composable +private fun Title( + text: String, + modifier: Modifier = Modifier, +) { + TextH40( + text = text, + textAlign = TextAlign.Center, + color = Color.White, + fontWeight = FontWeight.Bold, + disableScale = true, + modifier = modifier + ) +} + +@Composable +private fun Duration( + text: String, + modifier: Modifier = Modifier, +) { + TextH70( + text = text, + textAlign = TextAlign.Center, + color = Color.White, + disableScale = true, + modifier = modifier + ) +} + +@Composable +private fun Pillar( + text: String, + width: Dp, + height: Dp, + modifier: Modifier = Modifier, +) { + val pillarTopAspectRatio = PillarTopAspectRatio + val pillarTopHeight = width * pillarTopAspectRatio + Box { + Box( + modifier = modifier + .width(width) + .height(height) + .padding(top = pillarTopHeight / 2) + .dynamicBackground( + baseColor = PillarColor, + colorStops = listOf(Color.Black, Color.Transparent), + direction = FadeDirection.TopToBottom + ) + ) + Box { + Image( + painter = painterResource(R.drawable.rectangle), + modifier = modifier.width(width), + contentDescription = null, + contentScale = ContentScale.FillWidth + ) + Box( + contentAlignment = Alignment.Center, + modifier = modifier + .width(width) + .height(pillarTopHeight) + .graphicsLayer { + rotationZ = PillarRotationAngle + } + .drawWithContent { + withTransform( + { + val transformMatrix = Matrix() + transformMatrix.values[Matrix.SkewX] = PillarTextSkew + transform(transformMatrix) + } + ) { + this@drawWithContent.drawContent() + } + } + .offset(x = -width * 0.15f) + ) { + TextH30( + text = text, + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + disableScale = true, + color = Color.White, + ) + } + } + } +} diff --git a/modules/features/endofyear/src/main/java/au/com/shiftyjelly/pocketcasts/endofyear/utils/StoryModifierExtensions.kt b/modules/features/endofyear/src/main/java/au/com/shiftyjelly/pocketcasts/endofyear/utils/StoryModifierExtensions.kt index 04d6e01ef02..8d07c05f487 100644 --- a/modules/features/endofyear/src/main/java/au/com/shiftyjelly/pocketcasts/endofyear/utils/StoryModifierExtensions.kt +++ b/modules/features/endofyear/src/main/java/au/com/shiftyjelly/pocketcasts/endofyear/utils/StoryModifierExtensions.kt @@ -8,10 +8,16 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import au.com.shiftyjelly.pocketcasts.models.entity.Podcast +private const val Black50 = 0x80000000 + fun Modifier.podcastDynamicBackground(podcast: Podcast) = dynamicBackground(Color(podcast.getTintColor(false))) -fun Modifier.dynamicBackground(color: Color) = +fun Modifier.dynamicBackground( + baseColor: Color, + colorStops: List = listOf(Color.Black, Color(Black50)), + direction: FadeDirection = FadeDirection.BottomToTop, +) = graphicsLayer { /* https://rb.gy/iju6fn @@ -19,18 +25,19 @@ fun Modifier.dynamicBackground(color: Color) = The Clear blend mode will not work without it */ alpha = 0.99f }.drawWithContent { - val colors = listOf( - Color.Black, - Color(0x80000000), // 50% Black - ) - drawRect(color = color) + drawRect(color = baseColor) drawRect( brush = Brush.verticalGradient( - colors, - startY = Float.POSITIVE_INFINITY, - endY = 0f, + colorStops, + startY = if (direction == FadeDirection.BottomToTop) Float.POSITIVE_INFINITY else 0f, + endY = if (direction == FadeDirection.BottomToTop) 0f else Float.POSITIVE_INFINITY, ), blendMode = BlendMode.DstIn ) drawContent() } + +enum class FadeDirection { + TopToBottom, + BottomToTop +} diff --git a/modules/features/endofyear/src/main/java/au/com/shiftyjelly/pocketcasts/endofyear/views/stories/StoryTopListenedCategoriesView.kt b/modules/features/endofyear/src/main/java/au/com/shiftyjelly/pocketcasts/endofyear/views/stories/StoryTopListenedCategoriesView.kt index 802fdb4fda7..3697289af2f 100644 --- a/modules/features/endofyear/src/main/java/au/com/shiftyjelly/pocketcasts/endofyear/views/stories/StoryTopListenedCategoriesView.kt +++ b/modules/features/endofyear/src/main/java/au/com/shiftyjelly/pocketcasts/endofyear/views/stories/StoryTopListenedCategoriesView.kt @@ -1,130 +1,126 @@ package au.com.shiftyjelly.pocketcasts.endofyear.views.stories -import androidx.compose.foundation.Image +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.size import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material.Surface import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp -import au.com.shiftyjelly.pocketcasts.compose.AppTheme import au.com.shiftyjelly.pocketcasts.compose.components.TextH30 -import au.com.shiftyjelly.pocketcasts.compose.components.TextP40 -import au.com.shiftyjelly.pocketcasts.compose.preview.ThemePreviewParameterProvider +import au.com.shiftyjelly.pocketcasts.endofyear.components.CategoryPillar +import au.com.shiftyjelly.pocketcasts.endofyear.components.PodcastLogoWhite +import au.com.shiftyjelly.pocketcasts.endofyear.utils.dynamicBackground import au.com.shiftyjelly.pocketcasts.models.db.helper.ListenedCategory import au.com.shiftyjelly.pocketcasts.repositories.endofyear.stories.StoryTopListenedCategories -import au.com.shiftyjelly.pocketcasts.ui.theme.Theme -import au.com.shiftyjelly.pocketcasts.images.R as IR +import au.com.shiftyjelly.pocketcasts.settings.stats.StatsHelper +import au.com.shiftyjelly.pocketcasts.localization.R as LR + +private const val BackgroundColor = 0xFF744F9D @Composable fun StoryTopListenedCategoriesView( story: StoryTopListenedCategories, modifier: Modifier = Modifier, ) { - Column(modifier = modifier.verticalScroll(rememberScrollState())) { - TextH30( - text = "Your Top Categories", - textAlign = TextAlign.Center, - color = story.tintColor, - modifier = modifier - .fillMaxWidth() - .padding(vertical = 16.dp) - ) - story.listenedCategories.subList(0, minOf(story.listenedCategories.count(), 5)) - .mapIndexed { index, listenedCategory -> - CategoryItem( - listenedCategory = listenedCategory, - position = index, - tintColor = story.tintColor, - ) - } + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = modifier + .fillMaxSize() + .dynamicBackground(Color(BackgroundColor)) + .verticalScroll(rememberScrollState()) + ) { + Spacer(modifier = modifier.height(40.dp)) + + Spacer(modifier = modifier.weight(1f)) + + Title(story) + + Spacer(modifier = modifier.weight(0.5f)) + + CategoryPillars(story, modifier) + + Spacer(modifier = modifier.weight(1f)) + + PodcastLogoWhite() + + Spacer(modifier = modifier.height(40.dp)) } } @Composable -fun CategoryItem( - listenedCategory: ListenedCategory, - position: Int, - tintColor: Color, +private fun Title( + story: StoryTopListenedCategories, modifier: Modifier = Modifier, ) { - Column { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = modifier - .fillMaxWidth() - .padding(horizontal = 16.dp) - ) { - TextP40( - text = "${position + 1}", - color = tintColor, - modifier = modifier.padding(end = 16.dp) - ) - Row( - modifier = modifier - .padding(end = 16.dp) - .weight(1f), - verticalAlignment = Alignment.CenterVertically - ) { - Image( - painter = painterResource(IR.drawable.defaultartwork), - contentDescription = null, - modifier = modifier - .size(64.dp) - .padding(top = 4.dp, end = 12.dp, bottom = 4.dp) - ) - TextP40( - text = listenedCategory.category, - color = tintColor, - textAlign = TextAlign.Start, - modifier = modifier - .padding(end = 16.dp) - .weight(1f), - ) - Column { - TextP40( - text = "${listenedCategory.numberOfPodcasts}", - color = tintColor - ) - TextP40( - text = "Podcasts", - color = tintColor - ) - } - } - } - } + val text = stringResource(LR.string.end_of_year_story_top_categories) + TextH30( + text = text, + textAlign = TextAlign.Center, + color = story.tintColor, + modifier = modifier + .padding(horizontal = 40.dp) + .fillMaxWidth() + ) } -@Preview(showBackground = true) @Composable -private fun CategoryItemPreview( - @PreviewParameter(ThemePreviewParameterProvider::class) themeType: Theme.ThemeType, +private fun CategoryPillars( + story: StoryTopListenedCategories, + modifier: Modifier = Modifier, ) { - AppTheme(themeType) { - Surface(color = Color.Black) { - CategoryItem( - listenedCategory = ListenedCategory( - numberOfPodcasts = 2, - totalPlayedTime = 1L, - category = "News", - mostListenedPodcastId = "", - mostListenedPodcastTintColor = 0 - ), - position = 0, - tintColor = Color.White, + val context = LocalContext.current + Row( + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.Bottom, + modifier = modifier + .fillMaxWidth() + .padding(horizontal = 40.dp) + ) { + listOf(1, 0, 2).forEach { index -> + Spacer(modifier.weight(1f)) + + val listenedCategory = story.listenedCategories.atIndex(index) + listenedCategory?.let { + val timeText = + StatsHelper.secondsToFriendlyString( + listenedCategory.totalPlayedTime, + context.resources + ) + CategoryPillar( + title = listenedCategory.category, + duration = timeText, + text = (index + 1).toString(), + height = (200 - index * 55).dp, + modifier = modifier + .padding( + bottom = if (index == 0) 70.dp else 0.dp, + ) + ) + } ?: CategoryPillar( + title = "", + duration = "", + text = "", + height = 200.dp, + modifier = modifier.alpha(0f) ) + + Spacer(modifier.weight(1f)) } } } + +private fun List.atIndex(index: Int) = + if (index < size) this[index] else null diff --git a/modules/features/endofyear/src/main/res/drawable/rectangle.xml b/modules/features/endofyear/src/main/res/drawable/rectangle.xml new file mode 100644 index 00000000000..c4414b97428 --- /dev/null +++ b/modules/features/endofyear/src/main/res/drawable/rectangle.xml @@ -0,0 +1,9 @@ + + + diff --git a/modules/services/compose/src/main/java/au/com/shiftyjelly/pocketcasts/compose/components/TextStyles.kt b/modules/services/compose/src/main/java/au/com/shiftyjelly/pocketcasts/compose/components/TextStyles.kt index 4f650abeead..3515b98984d 100644 --- a/modules/services/compose/src/main/java/au/com/shiftyjelly/pocketcasts/compose/components/TextStyles.kt +++ b/modules/services/compose/src/main/java/au/com/shiftyjelly/pocketcasts/compose/components/TextStyles.kt @@ -7,6 +7,7 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily @@ -14,12 +15,21 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.sp import au.com.shiftyjelly.pocketcasts.compose.AppTheme import au.com.shiftyjelly.pocketcasts.compose.theme import au.com.shiftyjelly.pocketcasts.ui.theme.Theme import java.util.Locale +private val Float.nonScaledSp + @Composable + get() = (this / LocalDensity.current.fontScale).sp + +private val Int.nonScaledSp + @Composable + get() = (this / LocalDensity.current.fontScale).sp + @Composable fun TextH10( text: String, @@ -69,13 +79,15 @@ fun TextH30( textAlign: TextAlign? = null, color: Color = MaterialTheme.theme.colors.primaryText01, fontWeight: FontWeight? = null, - maxLines: Int = Int.MAX_VALUE + maxLines: Int = Int.MAX_VALUE, + disableScale: Boolean = false, + fontSize: TextUnit = 18.sp ) { Text( text = text, color = color, - fontSize = 18.sp, - lineHeight = 21.sp, + fontSize = if (disableScale) fontSize.value.nonScaledSp else fontSize, + lineHeight = if (disableScale) 21.nonScaledSp else 21.sp, textAlign = textAlign, fontWeight = fontWeight ?: FontWeight.SemiBold, maxLines = maxLines, @@ -90,14 +102,16 @@ fun TextH40( modifier: Modifier = Modifier, textAlign: TextAlign? = null, color: Color = MaterialTheme.theme.colors.primaryText01, - maxLines: Int = Int.MAX_VALUE + maxLines: Int = Int.MAX_VALUE, + fontWeight: FontWeight = FontWeight.Medium, + disableScale: Boolean = false ) { Text( text = text, color = color, - fontSize = 15.sp, - fontWeight = FontWeight.Medium, - lineHeight = 21.sp, + fontSize = if (disableScale) 15.nonScaledSp else 15.sp, + fontWeight = fontWeight, + lineHeight = if (disableScale) 21.nonScaledSp else 21.sp, textAlign = textAlign, maxLines = maxLines, overflow = TextOverflow.Ellipsis, @@ -217,15 +231,16 @@ fun TextH70( textAlign: TextAlign? = null, color: Color = MaterialTheme.theme.colors.primaryText01, fontWeight: FontWeight = FontWeight(500), - maxLines: Int = Int.MAX_VALUE + maxLines: Int = Int.MAX_VALUE, + disableScale: Boolean = false ) { Text( text = text, color = color, fontSize = 12.sp, fontWeight = fontWeight, - lineHeight = 14.sp, - letterSpacing = 0.25.sp, + lineHeight = if (disableScale) 14.nonScaledSp else 14.sp, + letterSpacing = if (disableScale) .25f.nonScaledSp else .25.sp, maxLines = maxLines, overflow = TextOverflow.Ellipsis, modifier = modifier, From 9e89767501b8775547125f31829652784cadbc80 Mon Sep 17 00:00:00 2001 From: ashiagr Date: Wed, 16 Nov 2022 18:42:53 +0530 Subject: [PATCH 2/2] Fix font size for duration Also increased width slightly for texts --- .../pocketcasts/endofyear/components/CategoryPillar.kt | 5 ++--- .../shiftyjelly/pocketcasts/compose/components/TextStyles.kt | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/features/endofyear/src/main/java/au/com/shiftyjelly/pocketcasts/endofyear/components/CategoryPillar.kt b/modules/features/endofyear/src/main/java/au/com/shiftyjelly/pocketcasts/endofyear/components/CategoryPillar.kt index 9ebe3af0c53..3b73a572707 100644 --- a/modules/features/endofyear/src/main/java/au/com/shiftyjelly/pocketcasts/endofyear/components/CategoryPillar.kt +++ b/modules/features/endofyear/src/main/java/au/com/shiftyjelly/pocketcasts/endofyear/components/CategoryPillar.kt @@ -58,13 +58,12 @@ fun CategoryPillar( Title( text = title, modifier = Modifier - .padding(bottom = 0.dp) - .width(width) + .width(width * 1.2f) ) Duration( text = duration, modifier = Modifier - .width(width) + .width(width * 1.2f) .alpha(0.8f) .padding(bottom = 30.dp) ) diff --git a/modules/services/compose/src/main/java/au/com/shiftyjelly/pocketcasts/compose/components/TextStyles.kt b/modules/services/compose/src/main/java/au/com/shiftyjelly/pocketcasts/compose/components/TextStyles.kt index 3515b98984d..d464cbd4cd2 100644 --- a/modules/services/compose/src/main/java/au/com/shiftyjelly/pocketcasts/compose/components/TextStyles.kt +++ b/modules/services/compose/src/main/java/au/com/shiftyjelly/pocketcasts/compose/components/TextStyles.kt @@ -237,7 +237,7 @@ fun TextH70( Text( text = text, color = color, - fontSize = 12.sp, + fontSize = if (disableScale) 12.nonScaledSp else 12.sp, fontWeight = fontWeight, lineHeight = if (disableScale) 14.nonScaledSp else 14.sp, letterSpacing = if (disableScale) .25f.nonScaledSp else .25.sp,