Improve performance across screens (#4071)

* Keep screens loaded while they're hidden and rebuild the library faster

* Count chapters in SQL for the migration list

* Show the extension list sooner and check trust against loaded stores

* Cancel the previous tracker search when searching again

* Write reader progress less often and keep its work off the main thread

* Stop rebuilding the settings search index on every key

* Watch updates for widgets only while one is placed

The query re-ran on every chapter or manga write even with no widget on
the home screen.

* Observe download pages and limit how often their progress updates

* Throttle the library update notification and write fetch data together
This commit is contained in:
AntsyLich
2026-10-11 04:28:52 +00:00
committed by GitHub
parent b15af71b94
commit 260e31cd2c
33 changed files with 458 additions and 136 deletions
@@ -1,9 +1,20 @@
package tachiyomi.presentation.widget
import android.content.Context
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.GlanceAppWidgetReceiver
class UpdatesGridCoverScreenGlanceReceiver : GlanceAppWidgetReceiver() {
override val glanceAppWidget: GlanceAppWidget
get() = UpdatesGridCoverScreenGlanceWidget()
override fun onEnabled(context: Context) {
super.onEnabled(context)
WidgetManager.onWidgetEnabled(glanceAppWidget)
}
override fun onDisabled(context: Context) {
super.onDisabled(context)
WidgetManager.onWidgetDisabled(glanceAppWidget)
}
}
@@ -1,5 +1,6 @@
package tachiyomi.presentation.widget
import android.content.Context
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.GlanceAppWidgetReceiver
@@ -7,4 +8,14 @@ class UpdatesGridGlanceReceiver : GlanceAppWidgetReceiver() {
override val glanceAppWidget: GlanceAppWidget
get() = UpdatesGridGlanceWidget()
override fun onEnabled(context: Context) {
super.onEnabled(context)
WidgetManager.onWidgetEnabled(glanceAppWidget)
}
override fun onDisabled(context: Context) {
super.onDisabled(context)
WidgetManager.onWidgetDisabled(glanceAppWidget)
}
}
@@ -1,16 +1,25 @@
package tachiyomi.presentation.widget
import android.content.Context
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.GlanceAppWidgetManager
import androidx.glance.appwidget.updateAll
import dev.zacsweers.metro.Inject
import eu.kanade.tachiyomi.core.security.SecurityPreferences
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import logcat.LogPriority
import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.updates.interactor.GetUpdates
@@ -21,21 +30,40 @@ class WidgetManager(
private val securityPreferences: SecurityPreferences,
) {
@OptIn(ExperimentalCoroutinesApi::class)
context(context: Context)
fun init(scope: CoroutineScope) {
combine(
getUpdates.subscribe(read = false, after = BaseUpdatesGridGlanceWidget.DateLimit),
securityPreferences.useAuthenticator.changes(),
transform = { a, b -> a to b },
)
scope.launch(Dispatchers.Default) {
val manager = GlanceAppWidgetManager(context)
val placed = widgets.filter { manager.getGlanceIds(it.javaClass).isNotEmpty() }
placedWidgets.update { it + placed.map { widget -> widget.javaClass } }
}
// Only watch updates while a widget is on the home screen
placedWidgets
.map { it.isNotEmpty() }
.distinctUntilChanged()
.flatMapLatest { hasWidgets ->
if (hasWidgets) {
combine(
getUpdates.subscribe(
read = false,
after = BaseUpdatesGridGlanceWidget.DateLimit,
),
securityPreferences.useAuthenticator.changes(),
transform = { a, b -> a to b },
)
} else {
emptyFlow()
}
}
.distinctUntilChanged { old, new ->
old.second == new.second &&
old.first.map { it.chapterId }.toSet() == new.first.map { it.chapterId }.toSet()
}
.onEach {
try {
UpdatesGridGlanceWidget().updateAll(context)
UpdatesGridCoverScreenGlanceWidget().updateAll(context)
widgets.forEach { it.updateAll(context) }
} catch (e: Exception) {
this.logcat(LogPriority.ERROR, e) { "Failed to update widget" }
}
@@ -43,4 +71,19 @@ class WidgetManager(
.flowOn(Dispatchers.Default)
.launchIn(scope)
}
companion object {
private val widgets: List<GlanceAppWidget>
get() = listOf(UpdatesGridGlanceWidget(), UpdatesGridCoverScreenGlanceWidget())
private val placedWidgets = MutableStateFlow(emptySet<Class<out GlanceAppWidget>>())
internal fun onWidgetEnabled(widget: GlanceAppWidget) {
placedWidgets.update { it + widget.javaClass }
}
internal fun onWidgetDisabled(widget: GlanceAppWidget) {
placedWidgets.update { it - widget.javaClass }
}
}
}