Skip to content

Commit

Permalink
refactor(webview): re-enable webview renderer (#568)
Browse files Browse the repository at this point in the history
* refactor(reading): reenable webview renderer

* refactor(reading): reenable webview renderer

* refactor(reading): reenable webview renderer

* refactor(reading): reenable webview renderer

* refactor(reading): reenable webview renderer

* refactor(reading): re-enable webview renderer

* refactor(webview): support rtl, video, preference styles
  • Loading branch information
Ashinch authored Aug 9, 2024
1 parent 2fc28bd commit b30ff86
Show file tree
Hide file tree
Showing 32 changed files with 1,381 additions and 300 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 @@ -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

0 comments on commit b30ff86

Please sign in to comment.