diff --git a/core/ui/src/main/kotlin/app/pachli/core/ui/OperationCounter.kt b/core/ui/src/main/kotlin/app/pachli/core/ui/OperationCounter.kt new file mode 100644 index 000000000..cf3405efa --- /dev/null +++ b/core/ui/src/main/kotlin/app/pachli/core/ui/OperationCounter.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2024 Pachli Association + * + * This file is a part of Pachli. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Pachli; if not, + * see . + */ + +package app.pachli.core.ui + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.getAndUpdate + +class OperationCounter { + private val _count: MutableStateFlow = MutableStateFlow(0) + + /** Count of outstanding operations. */ + val count = _count.asStateFlow() + + /** + * Runs [block] incrementing the operation count before [block] + * starts, decrementing it when [block] ends. + * + * ```kotlin + * private val operationCounter: OperationCounter() + * val operationCount = operationCounter.count + * + * suspend fun foo(): SomeType = operationCounter { + * some_network_operation() + * } + * ``` + * + * @return Whatever [block] returned + */ + suspend operator fun invoke(block: suspend () -> R): R { + _count.getAndUpdate { it + 1 } + val result = block.invoke() + _count.getAndUpdate { it - 1 } + return result + } +} diff --git a/feature/suggestions/src/main/kotlin/app/pachli/feature/suggestions/SuggestionsViewModel.kt b/feature/suggestions/src/main/kotlin/app/pachli/feature/suggestions/SuggestionsViewModel.kt index e2fc2c6f9..57ad3f876 100644 --- a/feature/suggestions/src/main/kotlin/app/pachli/feature/suggestions/SuggestionsViewModel.kt +++ b/feature/suggestions/src/main/kotlin/app/pachli/feature/suggestions/SuggestionsViewModel.kt @@ -27,6 +27,7 @@ import app.pachli.core.data.repository.StatusDisplayOptionsRepository import app.pachli.core.data.repository.SuggestionsError.DeleteSuggestionError import app.pachli.core.data.repository.SuggestionsError.FollowAccountError import app.pachli.core.data.repository.SuggestionsRepository +import app.pachli.core.ui.OperationCounter import app.pachli.feature.suggestions.UiAction.GetSuggestions import app.pachli.feature.suggestions.UiAction.SuggestionAction import app.pachli.feature.suggestions.UiAction.SuggestionAction.AcceptSuggestion @@ -42,10 +43,8 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filterIsInstance -import kotlinx.coroutines.flow.getAndUpdate import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.update @@ -128,8 +127,8 @@ internal class SuggestionsViewModel @Inject constructor( private val _uiResult = Channel>() override val uiResult = _uiResult.receiveAsFlow() - private val _operationCount = MutableStateFlow(0) - override val operationCount = _operationCount.asStateFlow() + private val operationCounter = OperationCounter() + override val operationCount = operationCounter.count private val reload = MutableSharedFlow(replay = 1) @@ -212,44 +211,28 @@ internal class SuggestionsViewModel @Inject constructor( } /** Get fresh suggestions from the repository. */ - private suspend fun getSuggestions(): Result = operation { - // Note: disabledSuggestions is *not* cleared here. Suppose the user has - // dismissed a suggestion and the network operation has not completed yet. - // They reload, and get a list of suggestions that includes the suggestion - // they have just dismissed. In that case the suggestion should still be - // disabled. - suggestionsRepository.getSuggestions().mapEither( - { Suggestions.Loaded(it.map { SuggestionViewData(suggestion = it) }) }, - { GetSuggestionsError(it) }, - ) - } + private suspend fun getSuggestions(): Result = + operationCounter { + // Note: disabledSuggestions is *not* cleared here. Suppose the user has + // dismissed a suggestion and the network operation has not completed yet. + // They reload, and get a list of suggestions that includes the suggestion + // they have just dismissed. In that case the suggestion should still be + // disabled. + suggestionsRepository.getSuggestions().mapEither( + { Suggestions.Loaded(it.map { SuggestionViewData(suggestion = it) }) }, + { GetSuggestionsError(it) }, + ) + } /** Delete a suggestion from the repository. */ - private suspend fun deleteSuggestion(suggestion: Suggestion): Result = operation { - suggestionsRepository.deleteSuggestion(suggestion.account.id) - } + private suspend fun deleteSuggestion(suggestion: Suggestion): Result = + operationCounter { + suggestionsRepository.deleteSuggestion(suggestion.account.id) + } /** Accept the suggestion and follow the account. */ - private suspend fun acceptSuggestion(suggestion: Suggestion): Result = operation { - suggestionsRepository.followAccount(suggestion.account.id) - } - - /** - * Runs [block] incrementing the network operation count before [block] - * starts, decrementing it when [block] ends. - * - * ```kotlin - * suspend fun foo(): SomeType = operation { - * some_network_operation() - * } - * ``` - * - * @return Whatever [block] returned - */ - private suspend fun operation(block: suspend () -> R): R { - _operationCount.getAndUpdate { it + 1 } - val result = block.invoke() - _operationCount.getAndUpdate { it - 1 } - return result - } + private suspend fun acceptSuggestion(suggestion: Suggestion): Result = + operationCounter { + suggestionsRepository.followAccount(suggestion.account.id) + } }