Wait for extensions to load before the list is propagated (#3841)
This commit is contained in:
@@ -6,7 +6,6 @@ import com.hippo.unifile.UniFile
|
|||||||
import dev.zacsweers.metro.AppScope
|
import dev.zacsweers.metro.AppScope
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
import dev.zacsweers.metro.SingleIn
|
import dev.zacsweers.metro.SingleIn
|
||||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -69,7 +68,6 @@ class DownloadCache(
|
|||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val provider: DownloadProvider,
|
private val provider: DownloadProvider,
|
||||||
private val sourceManager: SourceManager,
|
private val sourceManager: SourceManager,
|
||||||
private val extensionManager: ExtensionManager,
|
|
||||||
private val storageManager: StorageManager,
|
private val storageManager: StorageManager,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@@ -356,7 +354,6 @@ class DownloadCache(
|
|||||||
// Try to wait until extensions and sources have loaded
|
// Try to wait until extensions and sources have loaded
|
||||||
var sources = emptyList<Source>()
|
var sources = emptyList<Source>()
|
||||||
withTimeoutOrNull(30.seconds) {
|
withTimeoutOrNull(30.seconds) {
|
||||||
extensionManager.isInitialized.first { it }
|
|
||||||
sourceManager.isInitialized.first { it }
|
sourceManager.isInitialized.first { it }
|
||||||
|
|
||||||
sources = getSources()
|
sources = getSources()
|
||||||
|
|||||||
@@ -23,9 +23,10 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
|
||||||
import kotlinx.coroutines.flow.emptyFlow
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.onStart
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
@@ -55,19 +56,18 @@ class ExtensionManager(
|
|||||||
|
|
||||||
val scope = CoroutineScope(SupervisorJob())
|
val scope = CoroutineScope(SupervisorJob())
|
||||||
|
|
||||||
private val _isInitialized = MutableStateFlow(false)
|
private val isInitialized = MutableStateFlow(false)
|
||||||
val isInitialized: StateFlow<Boolean> = _isInitialized.asStateFlow()
|
|
||||||
|
|
||||||
private val iconMap = mutableMapOf<String, Drawable>()
|
private val iconMap = mutableMapOf<String, Drawable>()
|
||||||
|
|
||||||
private val installedExtensionMapFlow = MutableStateFlow(emptyMap<String, Extension.Installed>())
|
private val installedExtensionMapFlow = MutableStateFlow(emptyMap<String, Extension.Installed>())
|
||||||
val installedExtensionsFlow = installedExtensionMapFlow.mapExtensions(scope)
|
val installedExtensionsFlow = installedExtensionMapFlow.mapExtensionsOnceInitialized()
|
||||||
|
|
||||||
private val availableExtensionMapFlow = MutableStateFlow(emptyMap<String, Extension.Available>())
|
private val availableExtensionMapFlow = MutableStateFlow(emptyMap<String, Extension.Available>())
|
||||||
val availableExtensionsFlow = availableExtensionMapFlow.mapExtensions(scope)
|
val availableExtensionsFlow = availableExtensionMapFlow.mapExtensions(scope)
|
||||||
|
|
||||||
private val untrustedExtensionMapFlow = MutableStateFlow(emptyMap<String, Extension.Untrusted>())
|
private val untrustedExtensionMapFlow = MutableStateFlow(emptyMap<String, Extension.Untrusted>())
|
||||||
val untrustedExtensionsFlow = untrustedExtensionMapFlow.mapExtensions(scope)
|
val untrustedExtensionsFlow = untrustedExtensionMapFlow.mapExtensionsOnceInitialized()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
@@ -79,7 +79,7 @@ class ExtensionManager(
|
|||||||
private var subLanguagesEnabledOnFirstRun = preferences.enabledLanguages.isSet()
|
private var subLanguagesEnabledOnFirstRun = preferences.enabledLanguages.isSet()
|
||||||
|
|
||||||
fun getExtensionPackage(sourceId: Long): String? {
|
fun getExtensionPackage(sourceId: Long): String? {
|
||||||
return installedExtensionsFlow.value.find { extension ->
|
return installedExtensionMapFlow.value.values.find { extension ->
|
||||||
extension.sources.any { it.id == sourceId }
|
extension.sources.any { it.id == sourceId }
|
||||||
}
|
}
|
||||||
?.pkgName
|
?.pkgName
|
||||||
@@ -128,7 +128,7 @@ class ExtensionManager(
|
|||||||
.filterIsInstance<LoadResult.Untrusted>()
|
.filterIsInstance<LoadResult.Untrusted>()
|
||||||
.associate { it.extension.pkgName to it.extension }
|
.associate { it.extension.pkgName to it.extension }
|
||||||
|
|
||||||
_isInitialized.value = true
|
isInitialized.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -378,4 +378,12 @@ class ExtensionManager(
|
|||||||
private fun <T : Extension> StateFlow<Map<String, T>>.mapExtensions(scope: CoroutineScope): StateFlow<List<T>> {
|
private fun <T : Extension> StateFlow<Map<String, T>>.mapExtensions(scope: CoroutineScope): StateFlow<List<T>> {
|
||||||
return map { it.values.toList() }.stateIn(scope, SharingStarted.Lazily, value.values.toList())
|
return map { it.values.toList() }.stateIn(scope, SharingStarted.Lazily, value.values.toList())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extensions are loaded in the background, and [stateIn] would replay the empty list it was
|
||||||
|
* seeded with at construction, so this only starts emitting once that finished.
|
||||||
|
*/
|
||||||
|
private fun <T : Extension> StateFlow<Map<String, T>>.mapExtensionsOnceInitialized(): Flow<List<T>> {
|
||||||
|
return onStart { isInitialized.first { it } }.map { it.values.toList() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
@@ -48,8 +47,6 @@ class AndroidSourceManager(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
extensionManager.isInitialized.first { it }
|
|
||||||
|
|
||||||
extensionManager.installedExtensionsFlow
|
extensionManager.installedExtensionsFlow
|
||||||
.collectLatest { extensions ->
|
.collectLatest { extensions ->
|
||||||
val mutableMap = ConcurrentHashMap<Long, Source>(
|
val mutableMap = ConcurrentHashMap<Long, Source>(
|
||||||
|
|||||||
+19
-18
@@ -16,6 +16,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.filterNotNull
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -98,7 +99,7 @@ abstract class SearchViewModel(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSelectedSources(): List<Source> {
|
private suspend fun getSelectedSources(): List<Source> {
|
||||||
val enabledSources = getEnabledSources()
|
val enabledSources = getEnabledSources()
|
||||||
|
|
||||||
val filter = extensionFilter
|
val filter = extensionFilter
|
||||||
@@ -106,7 +107,7 @@ abstract class SearchViewModel(
|
|||||||
return enabledSources
|
return enabledSources
|
||||||
}
|
}
|
||||||
|
|
||||||
return extensionManager.installedExtensionsFlow.value
|
return extensionManager.installedExtensionsFlow.first()
|
||||||
.filter { it.pkgName == filter }
|
.filter { it.pkgName == filter }
|
||||||
.flatMap { it.sources }
|
.flatMap { it.sources }
|
||||||
.filter { it in enabledSources }
|
.filter { it in enabledSources }
|
||||||
@@ -139,23 +140,23 @@ abstract class SearchViewModel(
|
|||||||
|
|
||||||
searchJob?.cancel()
|
searchJob?.cancel()
|
||||||
|
|
||||||
val sources = getSelectedSources()
|
|
||||||
|
|
||||||
// Reuse previous results if possible
|
|
||||||
if (sameQuery) {
|
|
||||||
val existingResults = state.value.items
|
|
||||||
updateItems(
|
|
||||||
sources
|
|
||||||
.associateWith { existingResults[it] ?: SearchItemResult.Loading },
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
updateItems(
|
|
||||||
sources
|
|
||||||
.associateWith { SearchItemResult.Loading },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
searchJob = viewModelScope.launchIO {
|
searchJob = viewModelScope.launchIO {
|
||||||
|
val sources = getSelectedSources()
|
||||||
|
|
||||||
|
// Reuse previous results if possible
|
||||||
|
if (sameQuery) {
|
||||||
|
val existingResults = state.value.items
|
||||||
|
updateItems(
|
||||||
|
sources
|
||||||
|
.associateWith { existingResults[it] ?: SearchItemResult.Loading },
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
updateItems(
|
||||||
|
sources
|
||||||
|
.associateWith { SearchItemResult.Loading },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
sources.map { source ->
|
sources.map { source ->
|
||||||
async {
|
async {
|
||||||
if (state.value.items[source] !is SearchItemResult.Loading) {
|
if (state.value.items[source] !is SearchItemResult.Loading) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import eu.kanade.tachiyomi.util.system.WebViewUtil
|
|||||||
import eu.kanade.tachiyomi.util.system.createFileInCacheDir
|
import eu.kanade.tachiyomi.util.system.createFileInCacheDir
|
||||||
import eu.kanade.tachiyomi.util.system.toShareIntent
|
import eu.kanade.tachiyomi.util.system.toShareIntent
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.datetime.TimeZone
|
import kotlinx.datetime.TimeZone
|
||||||
import kotlinx.datetime.offsetAt
|
import kotlinx.datetime.offsetAt
|
||||||
import kotlinx.datetime.toLocalDateTime
|
import kotlinx.datetime.toLocalDateTime
|
||||||
@@ -62,10 +63,10 @@ class CrashLogUtil(
|
|||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getExtensionsInfo(): String? {
|
private suspend fun getExtensionsInfo(): String? {
|
||||||
val availableExtensions = extensionManager.availableExtensionsFlow.value.associateBy { it.pkgName }
|
val availableExtensions = extensionManager.availableExtensionsFlow.value.associateBy { it.pkgName }
|
||||||
|
|
||||||
val extensionInfoList = extensionManager.installedExtensionsFlow.value
|
val extensionInfoList = extensionManager.installedExtensionsFlow.first()
|
||||||
.sortedBy { it.name }
|
.sortedBy { it.name }
|
||||||
.mapNotNull {
|
.mapNotNull {
|
||||||
val availableExtension = availableExtensions[it.pkgName]
|
val availableExtension = availableExtensions[it.pkgName]
|
||||||
|
|||||||
Reference in New Issue
Block a user