Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(webview): re-enable webview renderer #568

Merged
merged 7 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package me.ash.reader.infrastructure.html


import net.dankito.readability4j.extended.processor.ArticleGrabberExtended
import net.dankito.readability4j.extended.util.RegExUtilExtended
import net.dankito.readability4j.model.ArticleGrabberOptions
import net.dankito.readability4j.model.ReadabilityOptions
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.jsoup.nodes.TextNode

open class RYArticleGrabberExtended(options: ReadabilityOptions, regExExtended: RegExUtilExtended) : ArticleGrabberExtended(options, regExExtended) {

override fun prepareNodes(doc: Document, options: ArticleGrabberOptions): List<Element> {
val elementsToScore = ArrayList<Element>()
var node: Element? = doc

while(node != null) {
val matchString = node.className() + " " + node.id()

// Check to see if this node is a byline, and remove it if it is.
if(checkByline(node, matchString)) {
node = removeAndGetNext(node, "byline")
continue
}

// Remove unlikely candidates
if(options.stripUnlikelyCandidates) {
if(regEx.isUnlikelyCandidate(matchString) &&
regEx.okMaybeItsACandidate(matchString) == false &&
node.tagName() != "body" &&
node.tagName() != "a") {
node = this.removeAndGetNext(node, "Removing unlikely candidate")
continue
}
}

// Remove DIV, SECTION, and HEADER nodes without any content(e.g. text, image, video, or iframe).
if((node.tagName() == "div" || node.tagName() == "section" || node.tagName() == "header" ||
node.tagName() == "h1" || node.tagName() == "h2" || node.tagName() == "h3" ||
node.tagName() == "h4" || node.tagName() == "h5" || node.tagName() == "h6") &&
this.isElementWithoutContent(node)) {
node = this.removeAndGetNext(node, "node without content")
continue
}

if(DEFAULT_TAGS_TO_SCORE.contains(node.tagName())) {
elementsToScore.add(node)
}

// Turn all divs that don't have children block level elements into p's
if(node.tagName() == "div") {
// Sites like http://mobile.slate.com encloses each paragraph with a DIV
// element. DIVs with only a P element inside and no text content can be
// safely converted into plain P elements to avoid confusing the scoring
// algorithm with DIVs with are, in practice, paragraphs.
if(this.hasSinglePInsideElement(node)) {
val newNode = node.child(0)
node.replaceWith(newNode)
node = newNode
elementsToScore.add(node)
}
else if(!this.hasChildBlockElement(node)) {
setNodeTag(node, "p")
elementsToScore.add(node)
}
else {
node.childNodes().forEach { childNode ->
if(childNode is TextNode && childNode.text().trim().length > 0) {
val p = doc.createElement("p")
p.text(childNode.text())
// EXPERIMENTAL
// p.attr("style", "display: inline;")
// p.addClass("readability-styled")
childNode.replaceWith(p)
}
}
}
}

node = if(node != null) this.getNextNode(node) else null
}

return elementsToScore
}
}
24 changes: 22 additions & 2 deletions app/src/main/java/me/ash/reader/infrastructure/html/Readability.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@ package me.ash.reader.infrastructure.html

import android.util.Log
import net.dankito.readability4j.extended.Readability4JExtended
import net.dankito.readability4j.extended.processor.PostprocessorExtended
import net.dankito.readability4j.extended.util.RegExUtilExtended
import net.dankito.readability4j.model.ReadabilityOptions
import net.dankito.readability4j.processor.MetadataParser
import net.dankito.readability4j.processor.Preprocessor
import org.jsoup.nodes.Element

object Readability {

fun parseToText(htmlContent: String?, uri: String?): String {
htmlContent ?: return ""
return try {
Readability4JExtended(uri ?: "", htmlContent).parse().textContent?.trim() ?: ""
Readability4JExtended(uri, htmlContent).parse().textContent?.trim() ?: ""
} catch (e: Exception) {
Log.e("RLog", "Readability.parseToText '$uri' is error: ", e)
""
Expand All @@ -19,10 +24,25 @@ object Readability {
fun parseToElement(htmlContent: String?, uri: String?): Element? {
htmlContent ?: return null
return try {
Readability4JExtended(uri ?: "", htmlContent).parse().articleContent
Readability4JExtended(uri, htmlContent).parse().articleContent
} catch (e: Exception) {
Log.e("RLog", "Readability.parseToElement '$uri' is error: ", e)
null
}
}

private fun Readability4JExtended(uri: String?, html: String): Readability4JExtended {
val options = ReadabilityOptions()
val regExUtil = RegExUtilExtended()
return Readability4JExtended(
uri = uri ?: "",
html = html,
options = options,
regExUtil = regExUtil,
preprocessor = Preprocessor(regExUtil),
metadataParser = MetadataParser(regExUtil),
articleGrabber = RYArticleGrabberExtended(options, regExUtil),
postprocessor = PostprocessorExtended(),
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ fun Preferences.toSettings(): Settings {
flowArticleListTonalElevation = FlowArticleListTonalElevationPreference.fromPreferences(this),

// Reading page
readingRenderer = ReadingRendererPreference.fromPreferences(this),
readingBionicReading = ReadingBionicReadingPreference.fromPreferences(this),
readingTheme = ReadingThemePreference.fromPreferences(this),
readingDarkTheme = ReadingDarkThemePreference.fromPreferences(this),
readingPageTonalElevation = ReadingPageTonalElevationPreference.fromPreferences(this),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package me.ash.reader.infrastructure.preference

import android.content.Context
import androidx.compose.runtime.compositionLocalOf
import androidx.datastore.preferences.core.Preferences
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import me.ash.reader.ui.ext.DataStoreKey
import me.ash.reader.ui.ext.DataStoreKey.Companion.readingBionicReading
import me.ash.reader.ui.ext.dataStore
import me.ash.reader.ui.ext.put

val LocalReadingBionicReading =
compositionLocalOf<ReadingBionicReadingPreference> { ReadingBionicReadingPreference.default }

sealed class ReadingBionicReadingPreference(val value: Boolean) : Preference() {
object ON : ReadingBionicReadingPreference(true)
object OFF : ReadingBionicReadingPreference(false)

override fun put(context: Context, scope: CoroutineScope) {
scope.launch {
context.dataStore.put(readingBionicReading, value)
}
}

companion object {

val default = OFF
val values = listOf(ON, OFF)

fun fromPreferences(preferences: Preferences) =
when (preferences[DataStoreKey.keys[readingBionicReading]?.key as Preferences.Key<Boolean>]) {
true -> ON
false -> OFF
else -> default
}
}
}

operator fun ReadingBionicReadingPreference.not(): ReadingBionicReadingPreference =
when (value) {
true -> ReadingBionicReadingPreference.OFF
false -> ReadingBionicReadingPreference.ON
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ val LocalReadingImageRoundedCorners =

object ReadingImageRoundedCornersPreference {

const val default = 32
const val default = 24

fun put(context: Context, scope: CoroutineScope, value: Int) {
scope.launch {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package me.ash.reader.infrastructure.preference

import android.content.Context
import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf
import androidx.datastore.preferences.core.Preferences
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import me.ash.reader.R
import me.ash.reader.ui.ext.DataStoreKey
import me.ash.reader.ui.ext.DataStoreKey.Companion.readingRenderer
import me.ash.reader.ui.ext.dataStore
import me.ash.reader.ui.ext.put

val LocalReadingRenderer =
compositionLocalOf<ReadingRendererPreference> { ReadingRendererPreference.default }

sealed class ReadingRendererPreference(val value: Int) : Preference() {
object WebView : ReadingRendererPreference(0)
object NativeComponent : ReadingRendererPreference(1)

override fun put(context: Context, scope: CoroutineScope) {
scope.launch {
context.dataStore.put(DataStoreKey.readingRenderer, value)
}
}

@Stable
fun toDesc(context: Context): String =
when (this) {
WebView -> context.getString(R.string.webview)
NativeComponent -> context.getString(R.string.native_component)
}

companion object {

val default = WebView
val values = listOf(WebView, NativeComponent)

fun fromPreferences(preferences: Preferences) =
when (preferences[DataStoreKey.keys[readingRenderer]?.key as Preferences.Key<Int>]) {
0 -> WebView
1 -> NativeComponent
else -> default
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ sealed class ReadingSubheadAlignPreference(val value: Int) : Preference() {
Justify -> TextAlign.Justify
}

fun toTextAlignCSS(): String =
when (this) {
Start -> "left"
End -> "right"
Center -> "center"
Justify -> "justify"
}

companion object {

val default = Start
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ sealed class ReadingTextAlignPreference(val value: Int) : Preference() {
Justify -> TextAlign.Justify
}

fun toTextAlignCSS(): String =
when (this) {
Start -> "start"
End -> "end"
Center -> "center"
Justify -> "justify"
}

fun toAlignment(): Alignment.Horizontal =
when (this) {
Start -> Alignment.Start
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import me.ash.reader.ui.ext.put
val LocalReadingTextLineHeight = compositionLocalOf { ReadingTextLineHeightPreference.default }

data object ReadingTextLineHeightPreference {
const val default = 1f
private val range = 0.8f..2f
const val default = 1.5F
private val range = 0.8F..2F

fun put(context: Context, scope: CoroutineScope, value: Float) {
scope.launch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ sealed class ReadingThemePreference(val value: Int) : Preference() {
ReadingTextHorizontalPaddingPreference.default)
ReadingTextAlignPreference.default.put(context, scope)
ReadingTextLetterSpacingPreference.put(context, scope, ReadingTextLetterSpacingPreference.default)
ReadingTextLineHeightPreference.put(context, scope, ReadingTextLineHeightPreference.default)
ReadingTextFontSizePreference.put(context, scope, ReadingTextFontSizePreference.default)
ReadingImageRoundedCornersPreference.put(context, scope, ReadingImageRoundedCornersPreference.default)
ReadingImageHorizontalPaddingPreference.put(context, scope,
Expand All @@ -69,7 +70,8 @@ sealed class ReadingThemePreference(val value: Int) : Preference() {
ReadingTextHorizontalPaddingPreference.default)
ReadingTextAlignPreference.default.put(context, scope)
ReadingTextLetterSpacingPreference.put(context, scope, ReadingTextLetterSpacingPreference.default)
ReadingTextFontSizePreference.put(context, scope, 18)
ReadingTextLineHeightPreference.put(context, scope, ReadingTextLineHeightPreference.default)
ReadingTextFontSizePreference.put(context, scope, 22)
ReadingImageRoundedCornersPreference.put(context, scope, 0)
ReadingImageHorizontalPaddingPreference.put(context, scope, 0)
ReadingImageMaximizePreference.default.put(context, scope)
Expand All @@ -87,6 +89,7 @@ sealed class ReadingThemePreference(val value: Int) : Preference() {
ReadingTextHorizontalPaddingPreference.default)
ReadingTextAlignPreference.Center.put(context, scope)
ReadingTextLetterSpacingPreference.put(context, scope, ReadingTextLetterSpacingPreference.default)
ReadingTextLineHeightPreference.put(context, scope, ReadingTextLineHeightPreference.default)
ReadingTextFontSizePreference.put(context, scope, 20)
ReadingImageRoundedCornersPreference.put(context, scope, 0)
ReadingImageHorizontalPaddingPreference.put(context, scope,
Expand All @@ -106,6 +109,7 @@ sealed class ReadingThemePreference(val value: Int) : Preference() {
ReadingTextHorizontalPaddingPreference.default)
ReadingTextAlignPreference.default.put(context, scope)
ReadingTextLetterSpacingPreference.put(context, scope, ReadingTextLetterSpacingPreference.default)
ReadingTextLineHeightPreference.put(context, scope, ReadingTextLineHeightPreference.default)
ReadingTextFontSizePreference.put(context, scope, ReadingTextFontSizePreference.default)
ReadingImageRoundedCornersPreference.put(context, scope, ReadingImageRoundedCornersPreference.default)
ReadingImageHorizontalPaddingPreference.put(context, scope,
Expand All @@ -117,7 +121,7 @@ sealed class ReadingThemePreference(val value: Int) : Preference() {

companion object {

val default = MaterialYou
val default = Reeder
val values = listOf(MaterialYou, Reeder, Paper, Custom)

fun fromPreferences(preferences: Preferences): ReadingThemePreference =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ data class Settings(
val flowArticleListReadIndicator: FlowArticleReadIndicatorPreference = FlowArticleReadIndicatorPreference.default,

// Reading page
val readingRenderer: ReadingRendererPreference = ReadingRendererPreference.default,
val readingBionicReading: ReadingBionicReadingPreference = ReadingBionicReadingPreference.default,
val readingTheme: ReadingThemePreference = ReadingThemePreference.default,
val readingDarkTheme: ReadingDarkThemePreference = ReadingDarkThemePreference.default,
val readingPageTonalElevation: ReadingPageTonalElevationPreference = ReadingPageTonalElevationPreference.default,
Expand Down Expand Up @@ -140,6 +142,8 @@ fun SettingsProvider(
LocalFlowArticleListReadIndicator provides settings.flowArticleListReadIndicator,

// Reading page
LocalReadingRenderer provides settings.readingRenderer,
LocalReadingBionicReading provides settings.readingBionicReading,
LocalReadingTheme provides settings.readingTheme,
LocalReadingDarkTheme provides settings.readingDarkTheme,
LocalReadingPageTonalElevation provides settings.readingPageTonalElevation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import androidx.compose.ui.unit.dp
fun CanBeDisabledIconButton(
modifier: Modifier = Modifier,
disabled: Boolean,
imageVector: ImageVector,
imageVector: ImageVector? = null,
icon: @Composable () -> Unit = {},
size: Dp = 24.dp,
contentDescription: String?,
tint: Color = LocalContentColor.current,
Expand All @@ -34,11 +35,15 @@ fun CanBeDisabledIconButton(
enabled = !disabled,
onClick = onClick,
) {
Icon(
modifier = Modifier.size(size),
imageVector = imageVector,
contentDescription = contentDescription,
tint = if (disabled) MaterialTheme.colorScheme.outline else tint,
)
if (imageVector != null) {
Icon(
modifier = Modifier.size(size),
imageVector = imageVector,
contentDescription = contentDescription,
tint = if (disabled) MaterialTheme.colorScheme.outline else tint,
)
} else {
icon()
}
}
}
}
Loading
Loading