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.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<Result<UiSuccess, UiError>>()
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<Unit>(replay = 1)
@ -212,44 +211,28 @@ internal class SuggestionsViewModel @Inject constructor(
}
/** Get fresh suggestions from the repository. */
private suspend fun getSuggestions(): Result<Suggestions.Loaded, GetSuggestionsError> = 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<Suggestions.Loaded, GetSuggestionsError> =
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<Unit, DeleteSuggestionError> = operation {
suggestionsRepository.deleteSuggestion(suggestion.account.id)
}
private suspend fun deleteSuggestion(suggestion: Suggestion): Result<Unit, DeleteSuggestionError> =
operationCounter {
suggestionsRepository.deleteSuggestion(suggestion.account.id)
}
/** Accept the suggestion and follow the account. */
private suspend fun acceptSuggestion(suggestion: Suggestion): Result<Unit, FollowAccountError> = 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 <R> 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<Unit, FollowAccountError> =
operationCounter {
suggestionsRepository.followAccount(suggestion.account.id)
}
}