refactor: Extract operation counter to a class (#977)

This commit is contained in:
Nik Clayton 2024-10-03 22:21:27 +02:00 committed by GitHub
parent 0d5d118267
commit cdbd0efe11
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 74 additions and 40 deletions

View File

@ -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 <http://www.gnu.org/licenses>.
*/
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<Int> = 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 <R> invoke(block: suspend () -> R): R {
_count.getAndUpdate { it + 1 }
val result = block.invoke()
_count.getAndUpdate { it - 1 }
return result
}
}

View File

@ -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.DeleteSuggestionError
import app.pachli.core.data.repository.SuggestionsError.FollowAccountError import app.pachli.core.data.repository.SuggestionsError.FollowAccountError
import app.pachli.core.data.repository.SuggestionsRepository 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.GetSuggestions
import app.pachli.feature.suggestions.UiAction.SuggestionAction import app.pachli.feature.suggestions.UiAction.SuggestionAction
import app.pachli.feature.suggestions.UiAction.SuggestionAction.AcceptSuggestion 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.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.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.getAndUpdate
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
@ -128,8 +127,8 @@ internal class SuggestionsViewModel @Inject constructor(
private val _uiResult = Channel<Result<UiSuccess, UiError>>() private val _uiResult = Channel<Result<UiSuccess, UiError>>()
override val uiResult = _uiResult.receiveAsFlow() override val uiResult = _uiResult.receiveAsFlow()
private val _operationCount = MutableStateFlow(0) private val operationCounter = OperationCounter()
override val operationCount = _operationCount.asStateFlow() override val operationCount = operationCounter.count
private val reload = MutableSharedFlow<Unit>(replay = 1) private val reload = MutableSharedFlow<Unit>(replay = 1)
@ -212,44 +211,28 @@ internal class SuggestionsViewModel @Inject constructor(
} }
/** Get fresh suggestions from the repository. */ /** Get fresh suggestions from the repository. */
private suspend fun getSuggestions(): Result<Suggestions.Loaded, GetSuggestionsError> = operation { private suspend fun getSuggestions(): Result<Suggestions.Loaded, GetSuggestionsError> =
// Note: disabledSuggestions is *not* cleared here. Suppose the user has operationCounter {
// dismissed a suggestion and the network operation has not completed yet. // Note: disabledSuggestions is *not* cleared here. Suppose the user has
// They reload, and get a list of suggestions that includes the suggestion // dismissed a suggestion and the network operation has not completed yet.
// they have just dismissed. In that case the suggestion should still be // They reload, and get a list of suggestions that includes the suggestion
// disabled. // they have just dismissed. In that case the suggestion should still be
suggestionsRepository.getSuggestions().mapEither( // disabled.
{ Suggestions.Loaded(it.map { SuggestionViewData(suggestion = it) }) }, suggestionsRepository.getSuggestions().mapEither(
{ GetSuggestionsError(it) }, { Suggestions.Loaded(it.map { SuggestionViewData(suggestion = it) }) },
) { GetSuggestionsError(it) },
} )
}
/** Delete a suggestion from the repository. */ /** Delete a suggestion from the repository. */
private suspend fun deleteSuggestion(suggestion: Suggestion): Result<Unit, DeleteSuggestionError> = operation { private suspend fun deleteSuggestion(suggestion: Suggestion): Result<Unit, DeleteSuggestionError> =
suggestionsRepository.deleteSuggestion(suggestion.account.id) operationCounter {
} suggestionsRepository.deleteSuggestion(suggestion.account.id)
}
/** Accept the suggestion and follow the account. */ /** Accept the suggestion and follow the account. */
private suspend fun acceptSuggestion(suggestion: Suggestion): Result<Unit, FollowAccountError> = operation { private suspend fun acceptSuggestion(suggestion: Suggestion): Result<Unit, FollowAccountError> =
suggestionsRepository.followAccount(suggestion.account.id) operationCounter {
} 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 <R> operation(block: suspend () -> R): R {
_operationCount.getAndUpdate { it + 1 }
val result = block.invoke()
_operationCount.getAndUpdate { it - 1 }
return result
}
} }