From 526aeb7915a50b8eb419bc0702e829ec61190aca Mon Sep 17 00:00:00 2001 From: Chris Arriola Date: Mon, 29 Apr 2024 11:25:52 -0700 Subject: [PATCH 1/4] [Jetcaster]: HTML format episode summary in podcast detail. --- .../jetcaster/ui/player/PlayerScreen.kt | 38 +++++-------------- .../jetcaster/ui/shared/EpisodeListItem.kt | 26 +++++++++---- .../example/jetcaster/ui/shared/HtmlText.kt | 28 ++++++++++++++ 3 files changed, 56 insertions(+), 36 deletions(-) create mode 100644 Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/shared/HtmlText.kt diff --git a/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/player/PlayerScreen.kt b/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/player/PlayerScreen.kt index e6aa79980b..478b971514 100644 --- a/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/player/PlayerScreen.kt +++ b/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/player/PlayerScreen.kt @@ -44,7 +44,6 @@ import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack @@ -76,7 +75,6 @@ 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.graphics.ColorFilter import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext @@ -85,13 +83,11 @@ import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import androidx.core.text.HtmlCompat import androidx.hilt.navigation.compose.hiltViewModel import androidx.window.core.layout.WindowSizeClass import androidx.window.core.layout.WindowWidthSizeClass @@ -103,6 +99,7 @@ import com.example.jetcaster.R import com.example.jetcaster.core.model.PlayerEpisode import com.example.jetcaster.core.player.EpisodePlayerState import com.example.jetcaster.designsystem.component.ImageBackgroundColorScrim +import com.example.jetcaster.ui.shared.HtmlTextContainer import com.example.jetcaster.ui.theme.JetcasterTheme import com.example.jetcaster.util.isBookPosture import com.example.jetcaster.util.isSeparatingPosture @@ -111,8 +108,8 @@ import com.example.jetcaster.util.verticalGradientScrim import com.google.accompanist.adaptive.HorizontalTwoPaneStrategy import com.google.accompanist.adaptive.TwoPane import com.google.accompanist.adaptive.VerticalTwoPaneStrategy -import java.time.Duration import kotlinx.coroutines.launch +import java.time.Duration /** * Stateful version of the Podcast player @@ -664,11 +661,13 @@ private fun PodcastInformation( maxLines = 1, overflow = TextOverflow.Ellipsis ) - HtmlText( - text = summary, - style = MaterialTheme.typography.bodyMedium, - color = LocalContentColor.current - ) + HtmlTextContainer(text = summary) { + Text( + text = it, + style = MaterialTheme.typography.bodyMedium, + color = LocalContentColor.current + ) + } } } @@ -826,25 +825,6 @@ private fun FullScreenLoading(modifier: Modifier = Modifier) { } } -@Composable -private fun HtmlText( - text: String, - style: TextStyle, - color: Color -) { - val annotationString = buildAnnotatedString { - val htmlCompat = HtmlCompat.fromHtml(text, HtmlCompat.FROM_HTML_MODE_COMPACT) - append(htmlCompat) - } - SelectionContainer { - Text( - text = annotationString, - style = style, - color = color - ) - } -} - @Preview @Composable fun TopAppBarPreview() { diff --git a/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/shared/EpisodeListItem.kt b/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/shared/EpisodeListItem.kt index f06c5ca692..3633b52652 100644 --- a/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/shared/EpisodeListItem.kt +++ b/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/shared/EpisodeListItem.kt @@ -201,13 +201,25 @@ private fun EpisodeListItemHeader( modifier = Modifier.padding(vertical = 2.dp) ) - Text( - text = if (showSummary) episode.summary else podcast.title, - maxLines = 2, - minLines = 1, - overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.titleSmall, - ) + if (showSummary) { + HtmlTextContainer(text = episode.summary) { + Text( + text = it, + maxLines = 2, + minLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.titleSmall, + ) + } + } else { + Text( + text = podcast.title, + maxLines = 2, + minLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.titleSmall, + ) + } } if (showPodcastImage) { EpisodeListItemImage( diff --git a/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/shared/HtmlText.kt b/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/shared/HtmlText.kt new file mode 100644 index 0000000000..f46100dd66 --- /dev/null +++ b/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/shared/HtmlText.kt @@ -0,0 +1,28 @@ +package com.example.jetcaster.ui.shared + +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.buildAnnotatedString +import androidx.core.text.HtmlCompat + +/** + * A container for text that should be HTML formatted. This container will handle building the + * annotated string from [text], and enable text selection if [text] has any selectable element. + */ +@Composable +fun HtmlTextContainer( + text: String, + content: @Composable (AnnotatedString) -> Unit +) { + val annotatedString = remember(key1 = text) { + buildAnnotatedString { + val htmlCompat = HtmlCompat.fromHtml(text, HtmlCompat.FROM_HTML_MODE_COMPACT) + append(htmlCompat) + } + } + SelectionContainer { + content(annotatedString) + } +} From 1d892613c960bca0fbe30bb656e23bfeff2b4901 Mon Sep 17 00:00:00 2001 From: arriolac Date: Mon, 29 Apr 2024 18:32:31 +0000 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=A4=96=20Apply=20Spotless?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/jetcaster/ui/player/PlayerScreen.kt | 2 +- .../com/example/jetcaster/ui/shared/HtmlText.kt | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/player/PlayerScreen.kt b/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/player/PlayerScreen.kt index 478b971514..19d0131beb 100644 --- a/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/player/PlayerScreen.kt +++ b/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/player/PlayerScreen.kt @@ -108,8 +108,8 @@ import com.example.jetcaster.util.verticalGradientScrim import com.google.accompanist.adaptive.HorizontalTwoPaneStrategy import com.google.accompanist.adaptive.TwoPane import com.google.accompanist.adaptive.VerticalTwoPaneStrategy -import kotlinx.coroutines.launch import java.time.Duration +import kotlinx.coroutines.launch /** * Stateful version of the Podcast player diff --git a/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/shared/HtmlText.kt b/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/shared/HtmlText.kt index f46100dd66..96dd85ed9a 100644 --- a/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/shared/HtmlText.kt +++ b/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/shared/HtmlText.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.example.jetcaster.ui.shared import androidx.compose.foundation.text.selection.SelectionContainer From e274b14c14d63b72ff8de224cc67868e3e756480 Mon Sep 17 00:00:00 2001 From: Chris Arriola Date: Tue, 30 Apr 2024 10:23:35 -0700 Subject: [PATCH 3/4] Moving HtmlTextContainer to designsystem. --- .../jetcaster/designsystem/component/HtmlTextContainer.kt} | 2 +- .../main/java/com/example/jetcaster/ui/player/PlayerScreen.kt | 2 +- .../java/com/example/jetcaster/ui/shared/EpisodeListItem.kt | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) rename Jetcaster/{mobile/src/main/java/com/example/jetcaster/ui/shared/HtmlText.kt => designsystem/src/main/java/com/example/jetcaster/designsystem/component/HtmlTextContainer.kt} (96%) diff --git a/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/shared/HtmlText.kt b/Jetcaster/designsystem/src/main/java/com/example/jetcaster/designsystem/component/HtmlTextContainer.kt similarity index 96% rename from Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/shared/HtmlText.kt rename to Jetcaster/designsystem/src/main/java/com/example/jetcaster/designsystem/component/HtmlTextContainer.kt index 96dd85ed9a..aefaed8968 100644 --- a/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/shared/HtmlText.kt +++ b/Jetcaster/designsystem/src/main/java/com/example/jetcaster/designsystem/component/HtmlTextContainer.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.example.jetcaster.ui.shared +package com.example.jetcaster.designsystem.component import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.runtime.Composable diff --git a/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/player/PlayerScreen.kt b/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/player/PlayerScreen.kt index 19d0131beb..4ff9d58d46 100644 --- a/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/player/PlayerScreen.kt +++ b/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/player/PlayerScreen.kt @@ -98,8 +98,8 @@ import coil.request.ImageRequest import com.example.jetcaster.R import com.example.jetcaster.core.model.PlayerEpisode import com.example.jetcaster.core.player.EpisodePlayerState +import com.example.jetcaster.designsystem.component.HtmlTextContainer import com.example.jetcaster.designsystem.component.ImageBackgroundColorScrim -import com.example.jetcaster.ui.shared.HtmlTextContainer import com.example.jetcaster.ui.theme.JetcasterTheme import com.example.jetcaster.util.isBookPosture import com.example.jetcaster.util.isSeparatingPosture diff --git a/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/shared/EpisodeListItem.kt b/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/shared/EpisodeListItem.kt index 3633b52652..89ecc7a9ff 100644 --- a/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/shared/EpisodeListItem.kt +++ b/Jetcaster/mobile/src/main/java/com/example/jetcaster/ui/shared/EpisodeListItem.kt @@ -53,6 +53,7 @@ import com.example.jetcaster.R import com.example.jetcaster.core.model.EpisodeInfo import com.example.jetcaster.core.model.PlayerEpisode import com.example.jetcaster.core.model.PodcastInfo +import com.example.jetcaster.designsystem.component.HtmlTextContainer import com.example.jetcaster.designsystem.component.PodcastImage import com.example.jetcaster.ui.home.PreviewEpisodes import com.example.jetcaster.ui.home.PreviewPodcasts From 0da24abf9a6dad20046d898d006c4e25d4e3e2df Mon Sep 17 00:00:00 2001 From: Chris Arriola Date: Thu, 2 May 2024 09:08:41 -0700 Subject: [PATCH 4/4] Add TODO comment.: --- .../jetcaster/designsystem/component/HtmlTextContainer.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Jetcaster/designsystem/src/main/java/com/example/jetcaster/designsystem/component/HtmlTextContainer.kt b/Jetcaster/designsystem/src/main/java/com/example/jetcaster/designsystem/component/HtmlTextContainer.kt index aefaed8968..502620e6bf 100644 --- a/Jetcaster/designsystem/src/main/java/com/example/jetcaster/designsystem/component/HtmlTextContainer.kt +++ b/Jetcaster/designsystem/src/main/java/com/example/jetcaster/designsystem/component/HtmlTextContainer.kt @@ -26,6 +26,10 @@ import androidx.core.text.HtmlCompat /** * A container for text that should be HTML formatted. This container will handle building the * annotated string from [text], and enable text selection if [text] has any selectable element. + * + * TODO: Remove/update once the project is using Compose 1.7 as that version provides improved + * support for HTML formatting. + * See: https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.7.0-alpha07 */ @Composable fun HtmlTextContainer(