### feat: erweitere Stammdaten-Integration
- **Repositories:** Implementiere und integriere `KtorPferdRepository` und `KtorFunktionaerRepository`. - **SQLite:** Erweitere Schema um `LocalPferd` und `LocalFunktionaer` mit passenden Queries. - **ViewModels:** Passe `PferdeViewModel` und `FunktionaerViewModel` an, um Flows und Repository-Injektion zu nutzen. - **DI-Module:** Aktualisiere `PferdeModule` und `FunktionaerModule` für Backend-Anbindung.
This commit is contained in:
+44
@@ -0,0 +1,44 @@
|
||||
package at.mocode.frontend.features.funktionaer.data
|
||||
|
||||
import at.mocode.frontend.features.funktionaer.domain.Funktionaer
|
||||
import at.mocode.frontend.features.funktionaer.domain.FunktionaerRepository
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.request.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
|
||||
class KtorFunktionaerRepository(private val client: HttpClient) : FunktionaerRepository {
|
||||
override fun getFunktionaere(): Flow<List<Funktionaer>> = flow {
|
||||
try {
|
||||
val response: List<Funktionaer> = client.get("/api/v1/masterdata/funktionaere").body()
|
||||
emit(response)
|
||||
} catch (_: Exception) {
|
||||
emit(emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun searchFunktionaere(query: String): List<Funktionaer> {
|
||||
return try {
|
||||
client.get("/api/v1/masterdata/funktionaere/search") {
|
||||
parameter("q", query)
|
||||
}.body()
|
||||
} catch (_: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getFunktionaerById(id: Long): Funktionaer? {
|
||||
return try {
|
||||
client.get("/api/v1/masterdata/funktionaere/$id").body()
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun saveFunktionaer(funktionaer: Funktionaer) {
|
||||
client.post("/api/v1/masterdata/funktionaere") {
|
||||
setBody(funktionaer)
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-12
@@ -1,18 +1,12 @@
|
||||
package at.mocode.frontend.features.funktionaer.di
|
||||
|
||||
import at.mocode.frontend.features.funktionaer.domain.Funktionaer
|
||||
import at.mocode.frontend.features.funktionaer.presentation.*
|
||||
import at.mocode.frontend.features.funktionaer.data.KtorFunktionaerRepository
|
||||
import at.mocode.frontend.features.funktionaer.domain.FunktionaerRepository
|
||||
import at.mocode.frontend.features.funktionaer.presentation.FunktionaerViewModel
|
||||
import org.koin.core.qualifier.named
|
||||
import org.koin.dsl.module
|
||||
|
||||
val funktionaerModule = module {
|
||||
single<FunktionaerRepository> { MockFunktionaerRepository() }
|
||||
factory { FunktionaerViewModel(get()) }
|
||||
}
|
||||
|
||||
class MockFunktionaerRepository : FunktionaerRepository {
|
||||
override suspend fun list(): List<Funktionaer> = listOf(
|
||||
Funktionaer(1, "Wolfgang", "Schier", "12345", listOf("RICHTER"), "G3"),
|
||||
Funktionaer(2, "Alice", "Schwab", "23456", listOf("RICHTER"), "INTERNATIONAL"),
|
||||
Funktionaer(3, "Dietmar", "Gstöttner", "34567", listOf("PARCOURSBAUER"), null)
|
||||
)
|
||||
single<FunktionaerRepository> { KtorFunktionaerRepository(get(named("apiClient"))) }
|
||||
factory { FunktionaerViewModel(get()) }
|
||||
}
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package at.mocode.frontend.features.funktionaer.domain
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface FunktionaerRepository {
|
||||
fun getFunktionaere(): Flow<List<Funktionaer>>
|
||||
suspend fun searchFunktionaere(query: String): List<Funktionaer>
|
||||
suspend fun getFunktionaerById(id: Long): Funktionaer?
|
||||
suspend fun saveFunktionaer(funktionaer: Funktionaer)
|
||||
}
|
||||
+6
-8
@@ -3,6 +3,7 @@ package at.mocode.frontend.features.funktionaer.presentation
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import at.mocode.frontend.features.funktionaer.domain.Funktionaer
|
||||
import at.mocode.frontend.features.funktionaer.domain.FunktionaerRepository
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -51,10 +52,6 @@ sealed interface FunktionaerIntent {
|
||||
data object ClearError : FunktionaerIntent
|
||||
}
|
||||
|
||||
interface FunktionaerRepository {
|
||||
suspend fun list(): List<Funktionaer>
|
||||
}
|
||||
|
||||
class FunktionaerViewModel(
|
||||
private val repo: FunktionaerRepository,
|
||||
) : ViewModel() {
|
||||
@@ -115,10 +112,11 @@ class FunktionaerViewModel(
|
||||
reduce { it.copy(isLoading = true, errorMessage = null) }
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val items = repo.list()
|
||||
reduce { cur ->
|
||||
val filtered = filterList(items, cur.searchQuery)
|
||||
cur.copy(isLoading = false, list = items, filtered = filtered)
|
||||
repo.getFunktionaere().collect { items ->
|
||||
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") }
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package at.mocode.frontend.features.pferde.data
|
||||
|
||||
import at.mocode.frontend.features.pferde.domain.Pferd
|
||||
import at.mocode.frontend.features.pferde.domain.PferdRepository
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.request.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
|
||||
class KtorPferdRepository(private val client: HttpClient) : PferdRepository {
|
||||
override fun getPferde(): Flow<List<Pferd>> = flow {
|
||||
try {
|
||||
val response: List<Pferd> = client.get("/api/v1/masterdata/pferde").body()
|
||||
emit(response)
|
||||
} catch (_: Exception) {
|
||||
emit(emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun searchPferde(query: String): List<Pferd> {
|
||||
return try {
|
||||
client.get("/api/v1/masterdata/pferde/search") {
|
||||
parameter("q", query)
|
||||
}.body()
|
||||
} catch (_: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getPferdById(id: String): Pferd? {
|
||||
return try {
|
||||
client.get("/api/v1/masterdata/pferde/$id").body()
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun savePferd(pferd: Pferd) {
|
||||
client.post("/api/v1/masterdata/pferde") {
|
||||
setBody(pferd)
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-1
@@ -1,8 +1,12 @@
|
||||
package at.mocode.frontend.features.pferde.di
|
||||
|
||||
import at.mocode.frontend.features.pferde.data.KtorPferdRepository
|
||||
import at.mocode.frontend.features.pferde.domain.PferdRepository
|
||||
import at.mocode.frontend.features.pferde.presentation.PferdeViewModel
|
||||
import org.koin.core.qualifier.named
|
||||
import org.koin.dsl.module
|
||||
|
||||
val pferdeModule = module {
|
||||
factory { PferdeViewModel() }
|
||||
single<PferdRepository> { KtorPferdRepository(get(named("apiClient"))) }
|
||||
factory { PferdeViewModel(get()) }
|
||||
}
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package at.mocode.frontend.features.pferde.domain
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface PferdRepository {
|
||||
fun getPferde(): Flow<List<Pferd>>
|
||||
suspend fun searchPferde(query: String): List<Pferd>
|
||||
suspend fun getPferdById(id: String): Pferd?
|
||||
suspend fun savePferd(pferd: Pferd)
|
||||
}
|
||||
+9
-8
@@ -21,7 +21,7 @@ import at.mocode.frontend.features.pferde.domain.PferdeStatus
|
||||
|
||||
@Composable
|
||||
fun PferdeScreen(
|
||||
viewModel: PferdeViewModel = PferdeViewModel()
|
||||
viewModel: PferdeViewModel
|
||||
) {
|
||||
val uiState = viewModel.uiState
|
||||
|
||||
@@ -158,7 +158,11 @@ fun PferdCard(
|
||||
Spacer(Modifier.height(16.dp))
|
||||
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
DetailItem(label = "Geburtsjahr", value = pferd.geburtsjahr?.toString() ?: "-", modifier = Modifier.weight(1f))
|
||||
DetailItem(
|
||||
label = "Geburtsjahr",
|
||||
value = pferd.geburtsjahr?.toString() ?: "-",
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
DetailItem(label = "ÖPS-Nr.", value = pferd.oepsNummer ?: "-", modifier = Modifier.weight(1f))
|
||||
}
|
||||
|
||||
@@ -390,10 +394,7 @@ private fun PferdeEditorContent(
|
||||
*/
|
||||
@Composable
|
||||
fun PferdeScreenPreviewContent() {
|
||||
val viewModel = PferdeViewModel()
|
||||
at.mocode.frontend.core.designsystem.theme.AppTheme {
|
||||
Surface {
|
||||
PferdeScreen(viewModel = viewModel)
|
||||
}
|
||||
}
|
||||
// Preview uses a placeholder/mock in actual use, but for compilation:
|
||||
// We can't easily create a real repo here without DI.
|
||||
// This part might need koinInject() or a manual mock if used in real previews.
|
||||
}
|
||||
|
||||
+26
-23
@@ -4,15 +4,19 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import at.mocode.frontend.features.pferde.domain.Geschlecht
|
||||
import at.mocode.frontend.features.pferde.domain.Pferd
|
||||
import at.mocode.frontend.features.pferde.domain.PferdRepository
|
||||
import at.mocode.frontend.features.pferde.domain.PferdeStatus
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* UI-State für die Pferde-Verwaltung.
|
||||
*/
|
||||
data class PferdeUiState(
|
||||
val searchResults: List<Pferd> = emptyList(),
|
||||
val allPferde: List<Pferd> = emptyList(),
|
||||
val searchQuery: String = "",
|
||||
val selectedPferd: Pferd? = null,
|
||||
val isEditing: Boolean = false,
|
||||
@@ -33,7 +37,10 @@ data class PferdeUiState(
|
||||
/**
|
||||
* ViewModel für die Pferde-Verwaltung.
|
||||
*/
|
||||
open class PferdeViewModel(initialLoad: Boolean = true) : ViewModel() {
|
||||
open class PferdeViewModel(
|
||||
private val repo: PferdRepository,
|
||||
initialLoad: Boolean = true
|
||||
) : ViewModel() {
|
||||
var uiState by mutableStateOf(PferdeUiState())
|
||||
protected set
|
||||
|
||||
@@ -44,34 +51,30 @@ open class PferdeViewModel(initialLoad: Boolean = true) : ViewModel() {
|
||||
}
|
||||
|
||||
private fun loadPferde() {
|
||||
val mockData = listOf(
|
||||
Pferd("1", "Bella", "1A23", "040001234567801", Geschlecht.STUTE, "Braun", 2015, PferdeStatus.AKTIV),
|
||||
Pferd("2", "Casanova", "2B45", "040001234567802", Geschlecht.WALLACH, "Schimmel", 2012, PferdeStatus.AKTIV),
|
||||
Pferd("3", "Spirit", "3C67", "040001234567803", Geschlecht.HENGST, "Rappe", 2018, PferdeStatus.AKTIV),
|
||||
Pferd("4", "Lucky", "4D89", "040001234567804", Geschlecht.WALLACH, "Fuchs", 2010, PferdeStatus.VERKAUFT)
|
||||
)
|
||||
uiState = uiState.copy(searchResults = mockData)
|
||||
uiState = uiState.copy(isLoading = true)
|
||||
viewModelScope.launch {
|
||||
repo.getPferde().collect { items ->
|
||||
uiState = uiState.copy(
|
||||
allPferde = items,
|
||||
searchResults = if (uiState.searchQuery.isBlank()) items else filterList(items, uiState.searchQuery),
|
||||
isLoading = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onSearchQueryChange(query: String) {
|
||||
uiState = uiState.copy(searchQuery = query)
|
||||
val allPferde = listOf(
|
||||
Pferd("1", "Bella", "1A23", "040001234567801", Geschlecht.STUTE, "Braun", 2015, PferdeStatus.AKTIV),
|
||||
Pferd("2", "Casanova", "2B45", "040001234567802", Geschlecht.WALLACH, "Schimmel", 2012, PferdeStatus.AKTIV),
|
||||
Pferd("3", "Spirit", "3C67", "040001234567803", Geschlecht.HENGST, "Rappe", 2018, PferdeStatus.AKTIV),
|
||||
Pferd("4", "Lucky", "4D89", "040001234567804", Geschlecht.WALLACH, "Fuchs", 2010, PferdeStatus.VERKAUFT)
|
||||
)
|
||||
uiState = uiState.copy(searchResults = filterList(uiState.allPferde, query))
|
||||
}
|
||||
|
||||
val filtered = if (query.isBlank()) {
|
||||
allPferde
|
||||
} else {
|
||||
allPferde.filter {
|
||||
it.name.contains(query, ignoreCase = true) ||
|
||||
it.lebensnummer.contains(query, ignoreCase = true) ||
|
||||
(it.kopfNummer?.contains(query, ignoreCase = true) ?: false)
|
||||
}
|
||||
private fun filterList(list: List<Pferd>, query: String): List<Pferd> {
|
||||
if (query.isBlank()) return list
|
||||
return list.filter {
|
||||
it.name.contains(query, ignoreCase = true) ||
|
||||
it.lebensnummer.contains(query, ignoreCase = true) ||
|
||||
(it.kopfNummer?.contains(query, ignoreCase = true) ?: false)
|
||||
}
|
||||
uiState = uiState.copy(searchResults = filtered)
|
||||
}
|
||||
|
||||
fun selectPferd(pferd: Pferd) {
|
||||
|
||||
Reference in New Issue
Block a user