Implement MVVM for all V3 screens: add ViewModels for Turniere, Bewerbe, Abteilungen, Pferde, Reiter, Vereins, and Funktionaer workflows. Update roadmap to mark B-1 tasks as complete.

This commit is contained in:
2026-04-03 00:21:09 +02:00
parent 48ffadaaa2
commit 2b3e2d8c1b
8 changed files with 748 additions and 8 deletions
@@ -0,0 +1,91 @@
package at.mocode.frontend.features.verein.presentation
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
data class VereinsListItem(
val id: Long,
val name: String,
val ort: String,
val oepsNummer: String,
)
data class VereinsState(
val isLoading: Boolean = false,
val searchQuery: String = "",
val list: List<VereinsListItem> = emptyList(),
val filtered: List<VereinsListItem> = emptyList(),
val selectedId: Long? = null,
val errorMessage: String? = null,
)
sealed interface VereinsIntent {
data object Load : VereinsIntent
data object Refresh : VereinsIntent
data class SearchChanged(val query: String) : VereinsIntent
data class Select(val id: Long?) : VereinsIntent
data object ClearError : VereinsIntent
}
interface VereinsRepository {
suspend fun list(): List<VereinsListItem>
}
class VereinsViewModel(
private val repo: VereinsRepository,
) {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
private val _state = MutableStateFlow(VereinsState(isLoading = true))
val state: StateFlow<VereinsState> = _state
init { send(VereinsIntent.Load) }
fun send(intent: VereinsIntent) {
when (intent) {
is VereinsIntent.Load, is VereinsIntent.Refresh -> load()
is VereinsIntent.SearchChanged -> reduce { it.copy(searchQuery = intent.query) }.also { filter() }
is VereinsIntent.Select -> reduce { it.copy(selectedId = intent.id) }
is VereinsIntent.ClearError -> reduce { it.copy(errorMessage = null) }
}
}
private fun load() {
reduce { it.copy(isLoading = true, errorMessage = null) }
scope.launch {
try {
val items = repo.list()
reduce { cur ->
val filtered = filterList(items, cur.searchQuery)
cur.copy(isLoading = false, list = items, filtered = filtered)
}
} catch (t: Throwable) {
reduce { it.copy(isLoading = false, errorMessage = t.message ?: "Fehler beim Laden") }
}
}
}
private fun filter() {
val cur = _state.value
val filtered = filterList(cur.list, cur.searchQuery)
reduce { it.copy(filtered = filtered) }
}
private fun filterList(list: List<VereinsListItem>, query: String): List<VereinsListItem> {
if (query.isBlank()) return list
val q = query.trim()
return list.filter {
it.name.contains(q, ignoreCase = true) ||
it.ort.contains(q, ignoreCase = true) ||
it.oepsNummer.contains(q, ignoreCase = true)
}
}
private inline fun reduce(block: (VereinsState) -> VereinsState) {
_state.value = block(_state.value)
}
}