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:
M57andM57 authored and GitHub committed 2026-07-22 15:03:30 +03:00
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,
)
}
}
}