Skip to content

Commit

Permalink
refactor(reading): re-enable webview renderer
Browse files Browse the repository at this point in the history
  • Loading branch information
Ashinch committed Aug 9, 2024
1 parent 5eb484e commit c8aae45
Show file tree
Hide file tree
Showing 27 changed files with 1,176 additions and 446 deletions.
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 @@ -54,6 +54,7 @@ fun Preferences.toSettings(): Settings {

// 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 @@ -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 @@ -52,6 +52,7 @@ data class Settings(

// 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 @@ -142,6 +143,7 @@ fun SettingsProvider(

// 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

0 comments on commit c8aae45

Please sign in to comment.