perf: optimize data loading across Config, System, Profiles, and Billing tabs (#678)
* perf(model): optimize model tab load speed with two-phase fetch and loadQuick * perf: optimize data loading across Config, System, Profiles, and Billing tabs * chore: remove leftover bottom nav references and unused updatePrimaryScreens * style: fix ktlint violations in Config/Profiles/Billing ViewModels - property/argument-list wrapping for async(...) calls - multiline-expression wrapping for async blocks - remove unused import in ConfigViewModel - break long error-message string literals to stay under 120 cols --------- Co-authored-by: M57 <Hy4ri@users.noreply.github.com>
This commit is contained in:
1 parent
f7afbfae85
commit
1cca7764cc
7 files changed
+222
-172
No files matched your search
@@ -8,6 +8,8 @@ import com.m57.hermescontrol.data.model.UsageBarsResponse
|
||||
import com.m57.hermescontrol.data.ws.BillingRepository
|
||||
import com.m57.hermescontrol.data.ws.HermesWsClient
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
@@ -47,54 +49,61 @@ class BillingViewModel : ViewModel() {
|
||||
_uiState.update { it.copy(isLoading = true, errorMessage = null, featureUnavailable = false) }
|
||||
loadJob =
|
||||
viewModelScope.launch {
|
||||
var sub: SubscriptionStateResponse? = null
|
||||
var bars: UsageBarsResponse? = null
|
||||
var unavailable = false
|
||||
var error: String? = null
|
||||
try {
|
||||
sub = BillingRepository.getSubscriptionState()
|
||||
// The live gateway returns an `ok:false` *result* (with
|
||||
// `error`/`message`) rather than an RPC-level error when the
|
||||
// backend feature is missing or the user isn't portal-authed.
|
||||
// Catch that here so the screen degrades gracefully.
|
||||
if (sub?.ok == false) {
|
||||
unavailable = true
|
||||
error = sub.error ?: "Billing unavailable"
|
||||
} else if (sub == null) {
|
||||
// Backend returned no result payload (null result / unmapped
|
||||
// response). Treat as a non-fatal load miss, not a crash.
|
||||
if (error == null) error = "No billing data returned"
|
||||
coroutineScope {
|
||||
val subDeferred =
|
||||
async {
|
||||
var sub: SubscriptionStateResponse? = null
|
||||
var unavailable = false
|
||||
var error: String? = null
|
||||
try {
|
||||
sub = BillingRepository.getSubscriptionState()
|
||||
if (sub?.ok == false) {
|
||||
unavailable = true
|
||||
error = sub.error ?: "Billing unavailable"
|
||||
} else if (sub == null) {
|
||||
if (error == null) error = "No billing data returned"
|
||||
}
|
||||
} catch (e: HermesWsClient.HermesRpcException) {
|
||||
unavailable = true
|
||||
error = e.message
|
||||
} catch (e: Exception) {
|
||||
if (e is kotlinx.coroutines.CancellationException) throw e
|
||||
error = e.message ?: "Failed to load subscription"
|
||||
}
|
||||
Triple(sub, unavailable, error)
|
||||
}
|
||||
|
||||
val barsDeferred =
|
||||
async {
|
||||
var bars: UsageBarsResponse? = null
|
||||
var error: String? = null
|
||||
try {
|
||||
bars = BillingRepository.getUsageBars()
|
||||
if (bars?.ok == false) {
|
||||
error = "Usage data unavailable"
|
||||
}
|
||||
} catch (e: HermesWsClient.HermesRpcException) {
|
||||
// usage unavailable is non-fatal
|
||||
} catch (e: Exception) {
|
||||
if (e is kotlinx.coroutines.CancellationException) throw e
|
||||
error = e.message ?: "Failed to load usage"
|
||||
}
|
||||
Pair(bars, error)
|
||||
}
|
||||
|
||||
val (sub, unavailable, subError) = subDeferred.await()
|
||||
val (bars, barsError) = barsDeferred.await()
|
||||
val error = subError ?: barsError
|
||||
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
isLoading = false,
|
||||
subscription = sub,
|
||||
usage = bars,
|
||||
featureUnavailable = unavailable,
|
||||
errorMessage = if (sub == null) error else null,
|
||||
)
|
||||
}
|
||||
} catch (e: HermesWsClient.HermesRpcException) {
|
||||
unavailable = true
|
||||
error = e.message
|
||||
} catch (e: Exception) {
|
||||
if (e is kotlinx.coroutines.CancellationException) throw e
|
||||
error = e.message ?: "Failed to load subscription"
|
||||
}
|
||||
try {
|
||||
bars = BillingRepository.getUsageBars()
|
||||
if (bars?.ok == false) {
|
||||
// usage unavailable is non-fatal — the plan card still renders
|
||||
if (error == null) error = "Usage data unavailable"
|
||||
}
|
||||
} catch (e: HermesWsClient.HermesRpcException) {
|
||||
// usage unavailable is non-fatal — do NOT mark the whole
|
||||
// feature unavailable; the plan card still renders.
|
||||
} catch (e: Exception) {
|
||||
if (e is kotlinx.coroutines.CancellationException) throw e
|
||||
if (error == null) error = e.message ?: "Failed to load usage"
|
||||
}
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
isLoading = false,
|
||||
subscription = sub,
|
||||
usage = bars,
|
||||
featureUnavailable = unavailable,
|
||||
// A failed primary subscription load is fatal — surface it
|
||||
// even if usage bars happened to load.
|
||||
errorMessage = if (sub == null) error else null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user