Make ExtensionStoresViewModel collect stores only while subscribed (#3717)

Assisted-by: Claude:claude-opus-5
This commit is contained in:
AntsyLich
2026-08-08 11:58:06 +06:00
committed by GitHub
parent 0834884335
commit f6aad7497e
2 changed files with 46 additions and 68 deletions
@@ -2,9 +2,9 @@ package eu.kanade.presentation.more.settings.screen.browse
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
@@ -27,7 +27,7 @@ class ExtensionStoresScreen(
val navigator = LocalNavigator.currentOrThrow
val viewModel = viewModel<ExtensionStoresViewModel>()
val state by viewModel.state.collectAsState()
val state by viewModel.state.collectAsStateWithLifecycle()
LaunchedEffect(url) {
url?.let { viewModel.addFromDeeplink(url) }
@@ -1,12 +1,19 @@
package eu.kanade.presentation.more.settings.screen.browse
import androidx.compose.runtime.Immutable
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import eu.kanade.tachiyomi.extension.ExtensionManager
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.WhileSubscribed
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import mihon.core.viewmodel.StateViewModel
import mihon.domain.extension.interactor.AddExtensionStore
import mihon.domain.extension.interactor.GetExtensionStores
import mihon.domain.extension.interactor.RemoveExtensionStore
@@ -15,6 +22,7 @@ import mihon.domain.extension.model.ExtensionStore
import tachiyomi.core.common.util.lang.launchIO
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import kotlin.time.Duration.Companion.seconds
class ExtensionStoresViewModel(
private val getExtensionStores: GetExtensionStores = Injekt.get(),
@@ -22,32 +30,18 @@ class ExtensionStoresViewModel(
private val removeExtensionStore: RemoveExtensionStore = Injekt.get(),
private val updateExtensionStores: UpdateExtensionStores = Injekt.get(),
private val extensionManager: ExtensionManager = Injekt.get(),
) : StateViewModel<ExtensionStoreScreenState>(ExtensionStoreScreenState.Loading) {
) : ViewModel() {
private inline fun updateSuccessState(
func: (ExtensionStoreScreenState.Success) -> ExtensionStoreScreenState.Success,
) {
mutableState.update {
when (it) {
ExtensionStoreScreenState.Loading -> it
is ExtensionStoreScreenState.Success -> func(it)
}
}
}
private val dialog = MutableStateFlow<ExtensionStoreDialog?>(null)
init {
viewModelScope.launchIO {
getExtensionStores.subscribe()
.collectLatest { stores ->
mutableState.update {
when (it) {
ExtensionStoreScreenState.Loading -> ExtensionStoreScreenState.Success(stores = stores)
is ExtensionStoreScreenState.Success -> it.copy(stores = stores)
}
}
}
}
val state: StateFlow<ExtensionStoreScreenState> = combine(
getExtensionStores.subscribe(),
dialog,
) { stores, dialog ->
ExtensionStoreScreenState.Success(stores = stores, dialog = dialog)
}
.flowOn(Dispatchers.IO)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5.seconds), ExtensionStoreScreenState.Loading)
/**
* Creates and adds a new repo to the database.
@@ -56,14 +50,12 @@ class ExtensionStoresViewModel(
*/
fun createRepo(baseUrl: String) {
viewModelScope.launch {
updateSuccessState {
it.copy(
dialog = when (it.dialog) {
is ExtensionStoreDialog.Create -> it.dialog.copy(processing = true)
is ExtensionStoreDialog.Confirm -> it.dialog.copy(processing = true)
else -> it.dialog
},
)
dialog.update {
when (it) {
is ExtensionStoreDialog.Create -> it.copy(processing = true)
is ExtensionStoreDialog.Confirm -> it.copy(processing = true)
else -> it
}
}
addExtensionStore(baseUrl)
.onSuccess {
@@ -71,20 +63,18 @@ class ExtensionStoresViewModel(
dismissDialog()
}
.onFailure { throwable ->
updateSuccessState {
it.copy(
dialog = when (it.dialog) {
is ExtensionStoreDialog.Create -> it.dialog.copy(
processing = false,
errorMessage = throwable.message ?: "unknown error",
)
is ExtensionStoreDialog.Confirm -> it.dialog.copy(
processing = false,
errorMessage = throwable.message ?: "unknown error",
)
else -> it.dialog
},
)
dialog.update {
when (it) {
is ExtensionStoreDialog.Create -> it.copy(
processing = false,
errorMessage = throwable.message ?: "unknown error",
)
is ExtensionStoreDialog.Confirm -> it.copy(
processing = false,
errorMessage = throwable.message ?: "unknown error",
)
else -> it
}
}
}
}
@@ -94,12 +84,8 @@ class ExtensionStoresViewModel(
* Refreshes information for each repository.
*/
fun refreshRepos() {
val status = state.value
if (status is ExtensionStoreScreenState.Success) {
viewModelScope.launchIO {
updateExtensionStores()
}
viewModelScope.launchIO {
updateExtensionStores()
}
}
@@ -114,26 +100,18 @@ class ExtensionStoresViewModel(
}
fun addFromDeeplink(storeIndexUrl: String) {
updateSuccessState { state ->
state.copy(
dialog = ExtensionStoreDialog.Confirm(
url = storeIndexUrl,
alreadyExists = state.stores.any { it.indexUrl == storeIndexUrl },
),
)
viewModelScope.launchIO {
val alreadyExists = getExtensionStores.get().any { it.indexUrl == storeIndexUrl }
dialog.update { ExtensionStoreDialog.Confirm(url = storeIndexUrl, alreadyExists = alreadyExists) }
}
}
fun showDialog(dialog: ExtensionStoreDialog) {
updateSuccessState { state ->
state.copy(dialog = dialog)
}
this.dialog.update { dialog }
}
fun dismissDialog() {
updateSuccessState {
it.copy(dialog = null)
}
dialog.update { null }
}
}