diff --git a/androidApp/src/main/java/dev/xinto/argos/ui/component/HtmlText2.kt b/androidApp/src/main/java/dev/xinto/argos/ui/component/HtmlText2.kt index e3e0f20..dd333f5 100644 --- a/androidApp/src/main/java/dev/xinto/argos/ui/component/HtmlText2.kt +++ b/androidApp/src/main/java/dev/xinto/argos/ui/component/HtmlText2.kt @@ -4,11 +4,14 @@ import android.annotation.SuppressLint import android.webkit.WebView import android.widget.FrameLayout import android.widget.FrameLayout.LayoutParams +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.width import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface +import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.remember @@ -16,10 +19,14 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.takeOrElse import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView @@ -32,13 +39,15 @@ fun MaterialHtmlText2( @Language("HTML") text: String, modifier: Modifier = Modifier, - userScrollEnabled: Boolean = true + userScrollEnabled: Boolean = true, + contentPadding: PaddingValues = PaddingValues(0.dp), ) { HtmlText2( modifier = modifier, text = text, - typography = HtmlText2Defaults.material3Typography(), - userScrollEnabled = userScrollEnabled + contentStyle = HtmlText2Defaults.material3Typography(), + userScrollEnabled = userScrollEnabled, + contentPadding = contentPadding ) } @@ -49,13 +58,27 @@ fun MaterialHtmlText2( fun HtmlText2( @Language("HTML") text: String, - typography: HtmlText2Typography, + contentStyle: HtmlText2ContentStyle, modifier: Modifier = Modifier, userScrollEnabled: Boolean = true, + contentPadding: PaddingValues = PaddingValues(0.dp), ) { val density = LocalDensity.current - val textCss = remember(density) { - typography.asCss(density) + val layoutDirection = LocalLayoutDirection.current + val textCss = remember(density, layoutDirection) { + contentStyle.asCss(density, layoutDirection) + } + val paddingCss = remember(layoutDirection) { + //li margin required to indent bullet-points + """ + body { + padding: ${contentPadding.toCss(layoutDirection)} + } + + li { + margin-inline-start: ${contentPadding.calculateStartPadding(layoutDirection).value}px + } + """.trimIndent() } val escapedText = remember { text.replace("#", "%23") @@ -86,6 +109,7 @@ fun HtmlText2( @@ -153,14 +177,25 @@ fun HtmlText2_Preview() { object HtmlText2Defaults { @Composable - fun material3Typography(color: Color = LocalContentColor.current): HtmlText2Typography { - return HtmlText2Typography( + fun material3Typography(color: Color = LocalContentColor.current): HtmlText2ContentStyle { + return HtmlText2ContentStyle( h1 = MaterialTheme.typography.displaySmall.toHtmlTextStyle(color), h2 = MaterialTheme.typography.headlineLarge.toHtmlTextStyle(color), h3 = MaterialTheme.typography.headlineMedium.toHtmlTextStyle(color), h4 = MaterialTheme.typography.headlineSmall.toHtmlTextStyle(color), h5 = MaterialTheme.typography.titleLarge.toHtmlTextStyle(color), - body = MaterialTheme.typography.bodyLarge.toHtmlTextStyle(color) + body = MaterialTheme.typography.bodyLarge.toHtmlTextStyle(color), + a = MaterialTheme.typography.bodyLarge.toHtmlTextStyle(color = MaterialTheme.colorScheme.primary), + table = HtmlText2TableStyle( + tableCornerRadius = 16.dp, + tableContentPadding = PaddingValues(horizontal = 24.dp, vertical = 16.dp), + headerFontSize = MaterialTheme.typography.titleMedium.fontSize, + headerBackgroundColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp), + bodyFontSize = MaterialTheme.typography.bodyMedium.fontSize, + bodyBackgroundColor = Color.Transparent, + textAlignment = TextAlign.Center, + dividerColor = MaterialTheme.colorScheme.outlineVariant + ) ) } @@ -174,8 +209,9 @@ object HtmlText2Defaults { } private interface CssRepresentable { + @Language("CSS") - fun asCss(density: Density): String + fun asCss(density: Density, layoutDirection: LayoutDirection): String } @Immutable @@ -186,53 +222,141 @@ data class HtmlText2TextStyle( ) : CssRepresentable { @Language("CSS") - override fun asCss(density: Density): String { - val (r, g, b, a) = textColor - val cssFontSize = "${fontSize.value}" + if (fontSize.isEm) "em" else "px" + override fun asCss(density: Density, layoutDirection: LayoutDirection): String { return """ - color: rgba(${r * 255f}, ${g * 255f}, ${b * 255f}, $a); + color: ${textColor.toCss()}; font-weight: ${fontWeight.weight}; - font-size: $cssFontSize; + font-size: ${fontSize.toCss()}; + """.trimIndent() + } +} + +@Immutable +data class HtmlText2TableStyle( + val tableCornerRadius: Dp, + val tableContentPadding: PaddingValues, + val headerFontSize: TextUnit, + val headerBackgroundColor: Color, + val bodyFontSize: TextUnit, + val bodyBackgroundColor: Color, + val textAlignment: TextAlign, + val dividerColor: Color +): CssRepresentable { + + @Language("CSS") + override fun asCss(density: Density, layoutDirection: LayoutDirection): String { + val cornerRadius = tableCornerRadius.toCssPx() + return """ + table { + border: 0; + border-spacing: 0; + font-size: ${bodyFontSize.toCss()}; + background-color: ${bodyBackgroundColor.toCss()}; + } + + table tr:first-child { + background-color: ${headerBackgroundColor.toCss()}; + font-size: ${headerFontSize.toCss()}; + } + + table td { + padding: ${tableContentPadding.toCss(layoutDirection)}; + text-align: ${textAlignment.toString().lowercase()}; + } + + table tr td { + border: 1px solid ${dividerColor.toCss()}; + } + + table tr:not(:last-child) td { + border-bottom: 0; + } + + table tr td:not(:last-child) { + border-right: 0; + } + + table tr:first-child td:first-child { + border-top-left-radius: ${cornerRadius}; + } + + table tr:first-child td:last-child { + border-top-right-radius: ${cornerRadius}; + } + + table tr:last-child td:first-child { + border-bottom-left-radius: ${cornerRadius}; + } + + table tr:last-child td:last-child { + border-bottom-right-radius: ${cornerRadius}; + } """.trimIndent() } } @Immutable -data class HtmlText2Typography( +data class HtmlText2ContentStyle( val h1: HtmlText2TextStyle, val h2: HtmlText2TextStyle, val h3: HtmlText2TextStyle, val h4: HtmlText2TextStyle, val h5: HtmlText2TextStyle, - val body: HtmlText2TextStyle + val body: HtmlText2TextStyle, + val a: HtmlText2TextStyle, + val table: HtmlText2TableStyle ) : CssRepresentable { @Language("CSS") - override fun asCss(density: Density): String { + override fun asCss(density: Density, layoutDirection: LayoutDirection): String { return """ * { margin: 0; padding: 0; } h1 { - ${h1.asCss(density)} + ${h1.asCss(density, layoutDirection)} } h2 { - ${h2.asCss(density)} + ${h2.asCss(density, layoutDirection)} } h3 { - ${h3.asCss(density)} + ${h3.asCss(density, layoutDirection)} } h4 { - ${h4.asCss(density)} + ${h4.asCss(density, layoutDirection)} } h5 { - ${h5.asCss(density)} + ${h5.asCss(density, layoutDirection)} } p, body { - ${body.asCss(density)} + ${body.asCss(density, layoutDirection)} + } + a { + ${a.asCss(density, layoutDirection)} } + ${table.asCss(density, layoutDirection)} """.trimIndent() } +} + +private fun Color.toCss(): String { + return "rgba(${red * 255f}, ${green * 255f}, ${blue * 255f}, $alpha)" +} + +private fun TextUnit.toCss(): String { + return value.toString() + if (isEm) "em" else "px" +} + +private fun Dp.toCssPx(): String { + return value.toString() + "px" +} + +private fun PaddingValues.toCss(layoutDirection: LayoutDirection): String { + val topPadding = calculateTopPadding().toCssPx() + val rightPadding = calculateRightPadding(layoutDirection).toCssPx() + val bottomPadding = calculateBottomPadding().toCssPx() + val leftPadding = calculateLeftPadding(layoutDirection).toCssPx() + return "$topPadding $rightPadding $bottomPadding $leftPadding" } \ No newline at end of file diff --git a/androidApp/src/main/java/dev/xinto/argos/ui/screen/course/page/syllabus/SyllabusPage.kt b/androidApp/src/main/java/dev/xinto/argos/ui/screen/course/page/syllabus/SyllabusPage.kt index d2d50ff..1a47f66 100644 --- a/androidApp/src/main/java/dev/xinto/argos/ui/screen/course/page/syllabus/SyllabusPage.kt +++ b/androidApp/src/main/java/dev/xinto/argos/ui/screen/course/page/syllabus/SyllabusPage.kt @@ -2,8 +2,8 @@ package dev.xinto.argos.ui.screen.course.page.syllabus import androidx.annotation.StringRes import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -87,9 +87,8 @@ fun SyllabusPage( } MaterialHtmlText2( text = text, - modifier = Modifier - .fillMaxSize() - .padding(16.dp), + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(16.dp) ) }