Refactor: Extract boilerplate: safeLaunchLoad, clearToast, ToastEffect (CODE-21/22/23) (#312)

- Extracted `safeLaunchLoad` inline extension function to `ViewModelExt.kt` to encapsulate API call success/failure handling logic and replaced usages across 18 viewmodels.
- Extracted `ToastHost` interface to standardize `clearToast()` behavior across all ViewModels displaying toasts.
- Extracted `ToastEffect` composable to `SharedComponents.kt` and applied it to 16 screens, reducing repetitive side effect code blocks.

Fixes #286
This commit is contained in:
M57
2026-06-22 23:54:42 +03:00
committed by GitHub
parent 6abfabdac0
commit 98c02879b4
36 changed files with 427 additions and 585 deletions
@@ -1,18 +1,14 @@
package com.m57.hermescontrol.ui.achievements
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.m57.hermescontrol.data.model.Achievement
import com.m57.hermescontrol.data.remote.ApiClient
import com.m57.hermescontrol.data.remote.NetworkResult
import com.m57.hermescontrol.data.remote.safeApiCall
import kotlinx.coroutines.Dispatchers
import com.m57.hermescontrol.ui.common.safeLaunchLoad
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
data class AchievementsUiState(
val isLoading: Boolean = false,
@@ -25,31 +21,25 @@ class AchievementsViewModel : ViewModel() {
val uiState: StateFlow<AchievementsUiState> = _uiState.asStateFlow()
fun loadAchievements() {
_uiState.update { it.copy(isLoading = true, errorMessage = null) }
viewModelScope.launch {
val result =
withContext(Dispatchers.IO) {
safeApiCall { ApiClient.hermesApi.getAchievements() }
safeLaunchLoad(
apiCall = { safeApiCall { ApiClient.hermesApi.getAchievements() } },
onStart = { _uiState.update { it.copy(isLoading = true, errorMessage = null) } },
onSuccess = { data ->
_uiState.update {
it.copy(
isLoading = false,
achievements = data.achievements.orEmpty(),
)
}
when (result) {
is NetworkResult.Success -> {
_uiState.update {
it.copy(
isLoading = false,
achievements = result.data.achievements.orEmpty(),
)
}
},
onError = { errorMsg ->
_uiState.update {
it.copy(
isLoading = false,
errorMessage = "Failed to load achievements: $errorMsg",
)
}
is NetworkResult.Failure -> {
_uiState.update {
it.copy(
isLoading = false,
errorMessage = "Failed to load achievements: ${result.error.message}",
)
}
}
}
}
},
)
}
}