diff --git a/app/src/main/java/me/ash/reader/ui/component/reader/HtmlToComposable.kt b/app/src/main/java/me/ash/reader/ui/component/reader/HtmlToComposable.kt index 043574a..5e46e04 100644 --- a/app/src/main/java/me/ash/reader/ui/component/reader/HtmlToComposable.kt +++ b/app/src/main/java/me/ash/reader/ui/component/reader/HtmlToComposable.kt @@ -30,7 +30,7 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.ClickableText import androidx.compose.foundation.text.selection.DisableSelection -import androidx.compose.material.Text +import androidx.compose.material3.Text import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -46,6 +46,7 @@ import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.BaselineShift import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.style.TextDirection import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import coil.size.Precision @@ -54,6 +55,8 @@ import coil.size.pxOrElse import me.ash.reader.R import me.ash.reader.infrastructure.preference.LocalReadingImageMaximize import me.ash.reader.ui.component.base.RYAsyncImage +import me.ash.reader.ui.ext.requiresBidi +import me.ash.reader.ui.theme.applyTextDirection import org.jsoup.Jsoup import org.jsoup.helper.StringUtil import org.jsoup.nodes.Element @@ -96,6 +99,8 @@ private fun LazyListScope.formatBody( val composer = TextComposer { paragraphBuilder -> item { val paragraph = paragraphBuilder.toAnnotatedString() + val requiresBidi = paragraph.toString().requiresBidi() + val textStyle = bodyStyle().applyTextDirection(requiresBidi = requiresBidi) // ClickableText prevents taps from deselecting selected text // So use regular Text if possible @@ -104,7 +109,7 @@ private fun LazyListScope.formatBody( ) { ClickableText( text = paragraph, - style = bodyStyle(), + style = textStyle, modifier = Modifier .padding(horizontal = textHorizontalPadding().dp) .width(MAX_CONTENT_WIDTH.dp) @@ -118,7 +123,7 @@ private fun LazyListScope.formatBody( } else { Text( text = paragraph, - style = bodyStyle(), + style = textStyle, modifier = Modifier .padding(horizontal = textHorizontalPadding().dp) .width(MAX_CONTENT_WIDTH.dp) diff --git a/app/src/main/java/me/ash/reader/ui/ext/StringExt.kt b/app/src/main/java/me/ash/reader/ui/ext/StringExt.kt index 104a3b7..321de61 100644 --- a/app/src/main/java/me/ash/reader/ui/ext/StringExt.kt +++ b/app/src/main/java/me/ash/reader/ui/ext/StringExt.kt @@ -4,6 +4,7 @@ import android.text.Html import android.util.Base64 import java.math.BigInteger import java.security.MessageDigest +import java.text.Bidi object MimeType { @@ -47,6 +48,9 @@ fun String?.decodeHTML(): String? = this?.run { Html.fromHtml(this).toString() } fun String?.orNotEmpty(l: (value: String) -> String): String = if (this.isNullOrBlank()) "" else l(this) + +fun String.requiresBidi(): Boolean = Bidi.requiresBidi(this.toCharArray(), 0, this.length) + fun String?.extractDomain(): String { if (this.isNullOrBlank()) return "" val urlMatchResult = Regex("(?<=://)([\\w\\d.-]+)").find(this) @@ -56,4 +60,4 @@ fun String?.extractDomain(): String { val domainRegex = Regex("[\\w\\d.-]+\\.[\\w\\d.-]+") val domainMatchResult = domainRegex.find(this) return domainMatchResult?.value ?: "" -} +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleItem.kt b/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleItem.kt index aca0bc0..5786db6 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleItem.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleItem.kt @@ -60,10 +60,10 @@ import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import coil.size.Precision import coil.size.Scale @@ -84,9 +84,11 @@ import me.ash.reader.ui.component.FeedIcon import me.ash.reader.ui.component.base.RYAsyncImage import me.ash.reader.ui.component.base.SIZE_1000 import me.ash.reader.ui.component.menu.AnimatedDropdownMenu +import me.ash.reader.ui.ext.requiresBidi import me.ash.reader.ui.ext.surfaceColorAtElevation import me.ash.reader.ui.page.settings.color.flow.generateArticleWithFeedPreview import me.ash.reader.ui.theme.Shape20 +import me.ash.reader.ui.theme.applyTextDirection import me.ash.reader.ui.theme.palette.onDark @Composable @@ -230,7 +232,7 @@ fun ArticleItem( Text( text = title, color = MaterialTheme.colorScheme.onSurface, - style = MaterialTheme.typography.titleMedium, + style = MaterialTheme.typography.titleMedium.applyTextDirection(title.requiresBidi()), maxLines = if (articleListDesc.value) 2 else 4, overflow = TextOverflow.Ellipsis, ) @@ -241,7 +243,9 @@ fun ArticleItem( modifier = Modifier.padding(top = 4.dp), text = shortDescription, color = MaterialTheme.colorScheme.onSurfaceVariant, - style = MaterialTheme.typography.bodySmall, + style = MaterialTheme.typography.bodySmall.applyTextDirection( + shortDescription.requiresBidi() + ), maxLines = 2, overflow = TextOverflow.Ellipsis, ) diff --git a/app/src/main/java/me/ash/reader/ui/page/home/reading/Metadata.kt b/app/src/main/java/me/ash/reader/ui/page/home/reading/Metadata.kt index fb865e6..ccf6551 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/reading/Metadata.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/reading/Metadata.kt @@ -11,11 +11,15 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextDirection import androidx.compose.ui.unit.dp import me.ash.reader.infrastructure.preference.* +import me.ash.reader.ui.component.reader.bodyStyle import me.ash.reader.ui.ext.formatAsString import me.ash.reader.ui.ext.openURL +import me.ash.reader.ui.ext.requiresBidi import me.ash.reader.ui.ext.roundClick +import me.ash.reader.ui.theme.applyTextDirection import java.util.* @Composable @@ -65,6 +69,8 @@ fun Metadata( style = MaterialTheme.typography.headlineLarge.copy( fontFamily = LocalReadingFonts.current.asFontFamily(context), fontWeight = if (titleBold.value) FontWeight.SemiBold else FontWeight.Normal, + ).applyTextDirection( + requiresBidi = title.requiresBidi() ), textAlign = titleAlign.toTextAlign(), ) diff --git a/app/src/main/java/me/ash/reader/ui/theme/Type.kt b/app/src/main/java/me/ash/reader/ui/theme/Type.kt index ed64555..05fb304 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/Type.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/Type.kt @@ -5,6 +5,7 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextDirection import androidx.compose.ui.unit.sp +import java.text.Bidi // TODO: Rename file to Typography.kt and add @Stable @@ -101,7 +102,16 @@ val SystemTypography = Typography( ), ) -internal fun TextStyle.applyTextDirection() = this.copy(textDirection = TextDirection.Content) +internal fun TextStyle.applyTextDirection(textDirection: TextDirection = TextDirection.Content) = + this.copy(textDirection = textDirection) + +/** + * Resolve the text to Rtl if the text requires BiDirectional + * @see [android.view.View.TEXT_DIRECTION_ANY_RTL] + * @see [Bidi.requiresBidi] + */ +fun TextStyle.applyTextDirection(requiresBidi: Boolean) = + this.applyTextDirection(textDirection = if (requiresBidi) TextDirection.Rtl else TextDirection.Ltr) internal fun Typography.applyTextDirection() = this.copy( displayLarge = displayLarge.applyTextDirection(),