Skip to content

Commit

Permalink
Render using yeeted toAnnotatedString
Browse files Browse the repository at this point in the history
  • Loading branch information
imashnake0 committed Dec 28, 2023
1 parent bc81a36 commit 9afee20
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 12 deletions.
2 changes: 2 additions & 0 deletions api/anilist/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ kotlin {
}

dependencies {
implementation(project(":core"))

// Apollo Kotlin
implementation(libs.apollo.runtime)
implementation(libs.apollo.cache.memory)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.imashnake.animite.api.anilist.sanitize.media

import android.graphics.Color
import android.text.Html
import android.text.Spanned
import com.imashnake.animite.api.anilist.MediaListQuery
import com.imashnake.animite.api.anilist.MediaQuery
import com.imashnake.animite.api.anilist.type.MediaRankType
Expand All @@ -19,7 +21,7 @@ data class Media(
val title: String?,
/** TODO: https://github.com/imashnake0/Animite/issues/58.
* @see MediaQuery.Media.description */
val description: String,
val description: Spanned,
/** @see MediaQuery.Media.rankings */
val rankings: List<Ranking>,
/** @see MediaQuery.Media.genres */
Expand Down Expand Up @@ -72,14 +74,17 @@ data class Media(
coverImage = query.coverImage?.extraLarge ?: query.coverImage?.large ?: query.coverImage?.medium,
color = query.coverImage?.color?.let { Color.parseColor(it) } ?: Color.TRANSPARENT,
title = query.title?.romaji ?: query.title?.english ?: query.title?.native,
description = query.description?.let {
description = query.description.let {
val flavour = CommonMarkFlavourDescriptor()
HtmlGenerator(
markdownText = it,
root = MarkdownParser(flavour).buildMarkdownTreeFromString(it),
flavour = flavour
).generateHtml()
}.orEmpty(),
Html.fromHtml(
HtmlGenerator(
markdownText = it.orEmpty(),
root = MarkdownParser(flavour).buildMarkdownTreeFromString(it.orEmpty()),
flavour = flavour
).generateHtml(),
Html.FROM_HTML_MODE_LEGACY
)
},
rankings = if (query.rankings == null) { emptyList() } else {
// TODO: Is this filter valid?
query.rankings.filter {
Expand Down
118 changes: 118 additions & 0 deletions app/src/main/java/com/imashnake/animite/dev/ext/SpannableExt.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package com.imashnake.animite.dev.ext

import android.graphics.Typeface
import android.text.Spanned
import android.text.style.CharacterStyle
import android.text.style.ForegroundColorSpan
import android.text.style.StyleSpan
import android.text.style.URLSpan
import android.text.style.UnderlineSpan
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration

/**
* [Yeeted](https://stackoverflow.com/a/74100225/11944949).
*/
fun Spanned.toAnnotatedString(primaryColor: Color): AnnotatedString {
val builder = AnnotatedString.Builder(this.toString())
val copierContext = CopierContext(primaryColor)
SpanCopier.entries.forEach { copier ->
getSpans(0, length, copier.spanClass).forEach { span ->
copier.copySpan(span, getSpanStart(span), getSpanEnd(span), builder, copierContext)
}
}
return builder.toAnnotatedString()
}

private data class CopierContext(
val primaryColor: Color,
)

private enum class SpanCopier {
URL {
override val spanClass = URLSpan::class.java
override fun copySpan(
span: Any,
start: Int,
end: Int,
destination: AnnotatedString.Builder,
context: CopierContext
) {
val urlSpan = span as URLSpan
destination.addStringAnnotation(
tag = name,
annotation = urlSpan.url,
start = start,
end = end,
)
destination.addStyle(
style = SpanStyle(color = context.primaryColor, textDecoration = TextDecoration.Underline),
start = start,
end = end,
)
}
},
FOREGROUND_COLOR {
override val spanClass = ForegroundColorSpan::class.java
override fun copySpan(
span: Any,
start: Int,
end: Int,
destination: AnnotatedString.Builder,
context: CopierContext
) {
val colorSpan = span as ForegroundColorSpan
destination.addStyle(
style = SpanStyle(color = Color(colorSpan.foregroundColor)),
start = start,
end = end,
)
}
},
UNDERLINE {
override val spanClass = UnderlineSpan::class.java
override fun copySpan(
span: Any,
start: Int,
end: Int,
destination: AnnotatedString.Builder,
context: CopierContext
) {
destination.addStyle(
style = SpanStyle(textDecoration = TextDecoration.Underline),
start = start,
end = end,
)
}
},
STYLE {
override val spanClass = StyleSpan::class.java
override fun copySpan(
span: Any,
start: Int,
end: Int,
destination: AnnotatedString.Builder,
context: CopierContext
) {
val styleSpan = span as StyleSpan

destination.addStyle(
style = when (styleSpan.style) {
Typeface.ITALIC -> SpanStyle(fontStyle = FontStyle.Italic)
Typeface.BOLD -> SpanStyle(fontWeight = FontWeight.Bold)
Typeface.BOLD_ITALIC -> SpanStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Italic)
else -> SpanStyle()
},
start = start,
end = end,
)
}
};

abstract val spanClass: Class<out CharacterStyle>
abstract fun copySpan(span: Any, start: Int, end: Int, destination: AnnotatedString.Builder, context: CopierContext)
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
Expand All @@ -64,6 +66,7 @@ import com.imashnake.animite.core.extensions.bannerParallax
import com.imashnake.animite.core.extensions.landscapeCutoutPadding
import com.imashnake.animite.core.ui.ScrollableText
import com.imashnake.animite.core.ui.TranslucentStatusBarLayout
import com.imashnake.animite.dev.ext.toAnnotatedString
import com.imashnake.animite.dev.internal.Constants
import com.imashnake.animite.features.ui.MediaSmall
import com.imashnake.animite.features.ui.MediaSmallRow
Expand Down Expand Up @@ -112,8 +115,12 @@ fun MediaPage(
) {
MediaDetails(
title = media.title.orEmpty(),
description = media.description.orEmpty(),
// TODO Can we do something about this Modifier chain?
description = if (media.description == null) {
buildAnnotatedString {}
} else {
media.description.toAnnotatedString(MaterialTheme.colorScheme.onBackground)
},
// TODO: Can we do something about this Modifier chain?
modifier = Modifier
.padding(
start = dimensionResource(Res.dimen.large_padding)
Expand Down Expand Up @@ -239,7 +246,7 @@ fun MediaBanner(
@Composable
fun MediaDetails(
title: String,
description: String,
description: AnnotatedString,
modifier: Modifier = Modifier
) {
Column(modifier) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package com.imashnake.animite.features.media

import android.text.Spanned
import com.imashnake.animite.api.anilist.sanitize.media.Media

data class MediaUiState(
val bannerImage: String? = null,
val coverImage: String? = null,
val color: Int? = null,
val title: String? = null,
val description: String? = null,
val description: Spanned? = null,
val ranks: List<Media.Ranking>? = null,
val genres: List<String>? = null,
val characters: List<Media.Character>? = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Color.Companion.Transparent
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.unit.Dp
import com.imashnake.animite.core.R

Expand Down Expand Up @@ -70,3 +71,54 @@ fun ScrollableText(
) { }
}
}

@Composable
fun ScrollableText(
text: AnnotatedString,
modifier: Modifier = Modifier,
gradientSize: Dp = dimensionResource(R.dimen.edge_gradient_size),
gradientColor: Color = MaterialTheme.colorScheme.background
) {
Box(modifier) {
Text(
text = text,
color = MaterialTheme.colorScheme.onBackground.copy(
alpha = ContentAlpha.medium
),
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier
.verticalScroll(rememberScrollState())
.padding(vertical = gradientSize)
)

Box(
modifier = Modifier
.height(gradientSize)
.fillMaxWidth()
.align(Alignment.TopCenter)
.background(
Brush.verticalGradient(
listOf(
gradientColor,
Transparent
)
)
)
) { }

Box(
modifier = Modifier
.height(gradientSize)
.fillMaxWidth()
.align(Alignment.BottomCenter)
.background(
Brush.verticalGradient(
listOf(
Transparent,
gradientColor
)
)
)
) { }
}
}

0 comments on commit 9afee20

Please sign in to comment.