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 new file mode 100644 index 0000000000..502620e6bf --- /dev/null +++ b/Jetcaster/designsystem/src/main/java/com/example/jetcaster/designsystem/component/HtmlTextContainer.kt @@ -0,0 +1,48 @@ +/* + * 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.designsystem.component + +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. + * + * 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( + 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) + } +} 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 f7b8dc2dca..358155fba9 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,12 +83,10 @@ 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.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 @@ -101,6 +97,7 @@ 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.theme.JetcasterTheme import com.example.jetcaster.ui.tooling.DevicePreviews @@ -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..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 @@ -201,13 +202,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(