Skip to content
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
154 changes: 116 additions & 38 deletions PLAN.md

Large diffs are not rendered by default.

47 changes: 45 additions & 2 deletions android/app/src/main/kotlin/cc/hafa/reader/ReaderApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import android.app.Activity
import android.app.Application
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.os.Bundle
import android.util.Log
import cc.hafa.reader.core.DUE_CHECK_MS
import cc.hafa.reader.core.HttpFeedFetcher
import cc.hafa.reader.core.ReaderModel
import cc.hafa.reader.core.ReaderPreferences
Expand All @@ -25,7 +27,9 @@ import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
Expand All @@ -52,6 +56,12 @@ class ReaderApplication : Application() {
// Set once a screen shows, so sync listens to the account; a process started only to check feeds just uploads.
private val onScreen = AtomicBoolean(false)

// Activities started and not stopped; touched only on the main thread.
private var startedActivities = 0

// Checks due feeds while a screen shows, as the extension's timer does; null while none shows.
private var dueTimer: Job? = null

/** The reader's small choices on this device. */
val preferences: ReaderPreferences by lazy { SharedReaderPreferences(this) }

Expand All @@ -68,6 +78,11 @@ class ReaderApplication : Application() {
getSystemService(ConnectivityManager::class.java)?.restrictBackgroundStatus ==
ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED
},
online = {
val connectivity = getSystemService(ConnectivityManager::class.java)
connectivity?.getNetworkCapabilities(connectivity.activeNetwork)
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) == true
},
)
}

Expand Down Expand Up @@ -130,11 +145,39 @@ class ReaderApplication : Application() {
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) = Unit

override fun onActivityStarted(activity: Activity) {
onScreen.set(true)
// Coming back to a running app checks what fell due meanwhile, since Android may have held back the
// background worker; the first start leaves that to the checks the screen makes when it opens.
val returning = onScreen.getAndSet(true)
mutableSync.value?.enterForeground()
startedActivities += 1
if (dueTimer == null) {
dueTimer = scope.launch {
if (!returning) {
delay(DUE_CHECK_MS)
}
while (true) {
try {
model.queueDue()
} catch (cancelled: CancellationException) {
throw cancelled
} catch (failure: Exception) {
Log.w("Reader", "checking due feeds failed", failure)
}
delay(DUE_CHECK_MS)
}
}
}
}

override fun onActivityStopped(activity: Activity) = Unit
override fun onActivityStopped(activity: Activity) {
startedActivities -= 1
// A rotation or theme switch stops the activity and starts its replacement at once; the timer runs on.
if (startedActivities <= 0 && !activity.isChangingConfigurations) {
startedActivities = 0
dueTimer?.cancel()
dueTimer = null
}
}

override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) = Unit

Expand Down
15 changes: 10 additions & 5 deletions android/app/src/main/kotlin/cc/hafa/reader/ui/ArticleBody.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package cc.hafa.reader.ui

import cc.hafa.reader.core.ALT_TEXT_CLASS
import cc.hafa.reader.core.bestSrcsetCandidate
import cc.hafa.reader.core.pictureChoice
import org.jsoup.Jsoup
import org.jsoup.nodes.Element
import org.jsoup.nodes.TextNode
Expand Down Expand Up @@ -59,6 +59,10 @@ sealed interface HtmlBlock {
val link: String,
/** What the picture shows, for screen readers. */
val alt: String = "",
/** The image's `width` attribute in CSS pixels; 0 when it has none. */
val declaredWidth: Double = 0.0,
/** Pixel density of the picture at [source]. */
val density: Double = 1.0,
) : HtmlBlock

/** A table, row by row. */
Expand Down Expand Up @@ -102,10 +106,11 @@ private class Walker(private val blocks: MutableList<HtmlBlock>) {
val name = node.normalName()
if (name == "img") {
flush()
val best = bestSrcsetCandidate(node.attr("srcset"))
val source = best.ifEmpty { node.absUrl("src").ifEmpty { node.attr("src") } }
if (source.isNotEmpty()) {
blocks.add(HtmlBlock.Image(source, place.link, node.attr("alt").trim()))
val picture = pictureChoice(node)
if (picture.address.isNotEmpty()) {
blocks.add(
HtmlBlock.Image(picture.address, place.link, node.attr("alt").trim(), picture.declaredWidth, picture.density),
)
}
} else if (name == "br") {
inline.append("<br>")
Expand Down
Loading
Loading