Skip to content

Commit

Permalink
Address some review comments
Browse files Browse the repository at this point in the history
  • Loading branch information
Isira-Seneviratne committed Sep 6, 2024
1 parent 31d164d commit edf3782
Show file tree
Hide file tree
Showing 9 changed files with 279 additions and 57 deletions.
3 changes: 3 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,9 @@ dependencies {
// Coroutines interop
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-rx3:1.8.1'

// Custom browser tab
implementation 'androidx.browser:browser:1.8.0'

/** Debugging **/
// Memory leak detection
debugImplementation "com.squareup.leakcanary:leakcanary-object-watcher-android:${leakCanaryVersion}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import androidx.fragment.app.Fragment
import androidx.fragment.compose.content
import org.schabi.newpipe.extractor.stream.StreamInfo
import org.schabi.newpipe.ktx.serializable
import org.schabi.newpipe.ui.components.video.VideoDescriptionSection
import org.schabi.newpipe.ui.components.video.StreamDescriptionSection
import org.schabi.newpipe.ui.theme.AppTheme
import org.schabi.newpipe.util.KEY_INFO

Expand All @@ -22,7 +22,7 @@ class DescriptionFragment : Fragment() {
) = content {
AppTheme {
Surface(color = MaterialTheme.colorScheme.background) {
VideoDescriptionSection(requireArguments().serializable(KEY_INFO)!!)
StreamDescriptionSection(requireArguments().serializable(KEY_INFO)!!)
}
}
}
Expand Down

This file was deleted.

165 changes: 165 additions & 0 deletions app/src/main/java/org/schabi/newpipe/ui/components/common/Markdown.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package org.schabi.newpipe.ui.components.common

import android.graphics.Typeface
import android.text.Layout
import android.text.Spanned
import android.text.style.AbsoluteSizeSpan
import android.text.style.AlignmentSpan
import android.text.style.BackgroundColorSpan
import android.text.style.ForegroundColorSpan
import android.text.style.RelativeSizeSpan
import android.text.style.StrikethroughSpan
import android.text.style.StyleSpan
import android.text.style.SubscriptSpan
import android.text.style.SuperscriptSpan
import android.text.style.TypefaceSpan
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.LinkAnnotation
import androidx.compose.ui.text.LinkInteractionListener
import androidx.compose.ui.text.ParagraphStyle
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextLinkStyles
import androidx.compose.ui.text.font.FontFamily
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.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.em
import androidx.core.text.getSpans

// The code below is copied from Html.android.kt in the Compose Text library, with some minor
// changes.

internal fun Spanned.toAnnotatedString(
linkStyles: TextLinkStyles? = null,
linkInteractionListener: LinkInteractionListener? = null
): AnnotatedString {
return AnnotatedString.Builder(capacity = length)
.append(this)
.also {
it.addSpans(this, linkStyles, linkInteractionListener)
}
.toAnnotatedString()
}

private fun AnnotatedString.Builder.addSpans(
spanned: Spanned,
linkStyles: TextLinkStyles?,
linkInteractionListener: LinkInteractionListener?
) {
spanned.getSpans<Any>().forEach { span ->
addSpan(
span,
spanned.getSpanStart(span),
spanned.getSpanEnd(span),
linkStyles,
linkInteractionListener
)
}
}

private fun AnnotatedString.Builder.addSpan(
span: Any,
start: Int,
end: Int,
linkStyles: TextLinkStyles?,
linkInteractionListener: LinkInteractionListener?
) {
when (span) {
is AbsoluteSizeSpan -> {
// TODO: Add Compose's implementation when it is available.
}

is AlignmentSpan -> {
addStyle(span.toParagraphStyle(), start, end)
}

is BackgroundColorSpan -> {
addStyle(SpanStyle(background = Color(span.backgroundColor)), start, end)
}

is ForegroundColorSpan -> {
addStyle(SpanStyle(color = Color(span.foregroundColor)), start, end)
}

is RelativeSizeSpan -> {
addStyle(SpanStyle(fontSize = span.sizeChange.em), start, end)
}

is StrikethroughSpan -> {
addStyle(SpanStyle(textDecoration = TextDecoration.LineThrough), start, end)
}

is StyleSpan -> {
span.toSpanStyle()?.let { addStyle(it, start, end) }
}

is SubscriptSpan -> {
addStyle(SpanStyle(baselineShift = BaselineShift.Subscript), start, end)
}

is SuperscriptSpan -> {
addStyle(SpanStyle(baselineShift = BaselineShift.Superscript), start, end)
}

is TypefaceSpan -> {
addStyle(span.toSpanStyle(), start, end)
}

is UnderlineSpan -> {
addStyle(SpanStyle(textDecoration = TextDecoration.Underline), start, end)
}

is URLSpan -> {
span.url?.let { url ->
val link = LinkAnnotation.Url(url, linkStyles, linkInteractionListener)
addLink(link, start, end)
}
}
}
}

private fun AlignmentSpan.toParagraphStyle(): ParagraphStyle {
val alignment = when (this.alignment) {
Layout.Alignment.ALIGN_NORMAL -> TextAlign.Start
Layout.Alignment.ALIGN_CENTER -> TextAlign.Center
Layout.Alignment.ALIGN_OPPOSITE -> TextAlign.End
else -> TextAlign.Unspecified
}
return ParagraphStyle(textAlign = alignment)
}

private fun StyleSpan.toSpanStyle(): SpanStyle? {
return when (style) {
Typeface.BOLD -> SpanStyle(fontWeight = FontWeight.Bold)
Typeface.ITALIC -> SpanStyle(fontStyle = FontStyle.Italic)
Typeface.BOLD_ITALIC -> SpanStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Italic)
else -> null
}
}

private fun TypefaceSpan.toSpanStyle(): SpanStyle {
val fontFamily = when (family) {
FontFamily.Cursive.name -> FontFamily.Cursive
FontFamily.Monospace.name -> FontFamily.Monospace
FontFamily.SansSerif.name -> FontFamily.SansSerif
FontFamily.Serif.name -> FontFamily.Serif
else -> {
optionalFontFamilyFromName(family)
}
}
return SpanStyle(fontFamily = fontFamily)
}

private fun optionalFontFamilyFromName(familyName: String?): FontFamily? {
if (familyName.isNullOrEmpty()) return null
val typeface = Typeface.create(familyName, Typeface.NORMAL)
return typeface.takeIf {
typeface != Typeface.DEFAULT &&
typeface != Typeface.create(Typeface.DEFAULT, Typeface.NORMAL)
}?.let { FontFamily(it) }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package org.schabi.newpipe.ui.components.common

import android.content.res.Configuration
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextLinkStyles
import androidx.compose.ui.text.fromHtml
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.noties.markwon.Markwon
import io.noties.markwon.linkify.LinkifyPlugin
import org.schabi.newpipe.extractor.ServiceList
import org.schabi.newpipe.extractor.stream.Description
import org.schabi.newpipe.ui.components.common.link.YouTubeLinkHandler
import org.schabi.newpipe.ui.theme.AppTheme
import org.schabi.newpipe.util.NO_SERVICE_ID

@Composable
fun parseDescription(description: Description, serviceId: Int): AnnotatedString {
val context = LocalContext.current
val linkHandler = remember(serviceId) {
if (serviceId == ServiceList.YouTube.serviceId) {
YouTubeLinkHandler(context)
} else {
null
}
}
val styles = TextLinkStyles(SpanStyle(textDecoration = TextDecoration.Underline))

return remember(description) {
when (description.type) {
Description.HTML -> AnnotatedString.fromHtml(description.content, styles, linkHandler)
Description.MARKDOWN -> {
Markwon.builder(context)
.usePlugin(LinkifyPlugin.create())
.build()
.toMarkdown(description.content)
.toAnnotatedString(styles, linkHandler)
}
else -> AnnotatedString(description.content)
}
}
}

private class DescriptionPreviewProvider : PreviewParameterProvider<Description> {
override val values = sequenceOf(
Description("This is a description.", Description.PLAIN_TEXT),
Description("This is a <b>bold description</b>.", Description.HTML),
Description("This is a [link](https://example.com).", Description.MARKDOWN),
)
}

@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun ParseDescriptionPreview(
@PreviewParameter(DescriptionPreviewProvider::class) description: Description
) {
AppTheme {
Surface(color = MaterialTheme.colorScheme.background) {
Text(text = parseDescription(description, NO_SERVICE_ID))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.schabi.newpipe.ui.components.common.link

import android.content.Context
import androidx.browser.customtabs.CustomTabsIntent
import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.LinkInteractionListener
import androidx.core.net.toUri
import org.schabi.newpipe.extractor.ServiceList
import org.schabi.newpipe.util.NavigationHelper

class YouTubeLinkHandler(private val context: Context) : LinkInteractionListener {
override fun onClick(link: LinkAnnotation) {
val uri = (link as LinkAnnotation.Url).url.toUri()

// TODO: Redirect other links to NewPipe as well.
if ("hashtag" in uri.pathSegments) {
NavigationHelper.openSearch(
context, ServiceList.YouTube.serviceId, "#${uri.lastPathSegment}"
)
} else {
// Open link in custom browser tab.
CustomTabsIntent.Builder().build().launchUrl(context, uri)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ fun MetadataItem(@StringRes title: Int, value: AnnotatedString) {
Text(
modifier = Modifier.width(96.dp),
textAlign = TextAlign.End,
text = stringResource(title),
text = stringResource(title).uppercase(),
fontWeight = FontWeight.Bold
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ElevatedSuggestionChip
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SuggestionChip
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
Expand All @@ -34,14 +34,14 @@ fun TagsSection(serviceId: Int, tags: List<String>) {
Column(modifier = Modifier.padding(4.dp)) {
Text(
modifier = Modifier.fillMaxWidth(),
text = stringResource(R.string.metadata_tags),
text = stringResource(R.string.metadata_tags).uppercase(),
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center
)

FlowRow(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
for (tag in sortedTags) {
SuggestionChip(
ElevatedSuggestionChip(
onClick = {
NavigationHelper.openSearchFragment(
(context as FragmentActivity).supportFragmentManager, serviceId, tag
Expand Down
Loading

0 comments on commit edf3782

Please sign in to comment.