chore: refaktoriere Veranstaltungs-UI zu Events, implementiere ZNS-Suche und verbessere Navigationslogik
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
This commit is contained in:
+2
@@ -1,6 +1,7 @@
|
||||
package at.mocode.frontend.features.profile.di
|
||||
|
||||
import at.mocode.frontend.features.profile.data.ProfileApiClient
|
||||
import at.mocode.frontend.features.profile.presentation.ProfileOnboardingViewModel
|
||||
import at.mocode.frontend.features.profile.presentation.ProfileViewModel
|
||||
import org.koin.core.qualifier.named
|
||||
import org.koin.dsl.module
|
||||
@@ -8,4 +9,5 @@ import org.koin.dsl.module
|
||||
val profileModule = module {
|
||||
single { ProfileApiClient(get(named("apiClient")), get()) }
|
||||
single { ProfileViewModel(get()) }
|
||||
factory { ProfileOnboardingViewModel(get(), get()) }
|
||||
}
|
||||
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
package at.mocode.frontend.features.profile.presentation
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.CheckCircle
|
||||
import androidx.compose.material.icons.filled.Person
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import at.mocode.frontend.core.designsystem.components.MsTextField
|
||||
|
||||
@Composable
|
||||
fun ProfileOnboardingScreen(
|
||||
viewModel: ProfileOnboardingViewModel,
|
||||
onFinish: () -> Unit
|
||||
) {
|
||||
val state = viewModel.state
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize().padding(24.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Willkommen bei der Meldestelle",
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
|
||||
LinearProgressIndicator(
|
||||
progress = {
|
||||
when (state.currentStep) {
|
||||
OnboardingStep.SEARCH_ZNS -> 0.33f
|
||||
OnboardingStep.CONFIRM_DATA -> 0.66f
|
||||
OnboardingStep.FINISHED -> 1f
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
Box(modifier = Modifier.weight(1f)) {
|
||||
when (state.currentStep) {
|
||||
OnboardingStep.SEARCH_ZNS -> SearchStep(viewModel)
|
||||
OnboardingStep.CONFIRM_DATA -> ConfirmStep(viewModel)
|
||||
OnboardingStep.FINISHED -> FinishedStep(state, onFinish)
|
||||
}
|
||||
}
|
||||
|
||||
if (state.currentStep != OnboardingStep.FINISHED) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
OutlinedButton(onClick = { viewModel.back() }, enabled = state.currentStep != OnboardingStep.SEARCH_ZNS) {
|
||||
Text("Zurück")
|
||||
}
|
||||
if (state.currentStep == OnboardingStep.CONFIRM_DATA) {
|
||||
Button(onClick = { viewModel.confirmAndLink() }, enabled = !state.isLoading) {
|
||||
if (state.isLoading) CircularProgressIndicator(Modifier.size(16.dp))
|
||||
else Text("Daten bestätigen & Verknüpfen")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SearchStep(viewModel: ProfileOnboardingViewModel) {
|
||||
val state = viewModel.state
|
||||
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
Text("Wer bist du?", style = MaterialTheme.typography.titleLarge)
|
||||
Text("Suchen Sie nach Ihrer Satznummer oder Ihrem Namen in den ZNS-Stammdaten.")
|
||||
|
||||
MsTextField(
|
||||
value = state.searchQuery,
|
||||
onValueChange = { viewModel.onSearchQueryChange(it) },
|
||||
label = "Suche (Name oder Satznummer)",
|
||||
placeholder = "z.B. Stroblmair",
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
leadingIcon = Icons.Default.Search
|
||||
)
|
||||
|
||||
if (state.isLoading) {
|
||||
CircularProgressIndicator(modifier = Modifier.align(Alignment.CenterHorizontally))
|
||||
}
|
||||
|
||||
LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
items(state.searchResults) { reiter ->
|
||||
Card(
|
||||
onClick = { viewModel.selectReiter(reiter) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
Icon(Icons.Default.Person, null)
|
||||
Column {
|
||||
Text("${reiter.vorname} ${reiter.nachname}", fontWeight = FontWeight.Bold)
|
||||
Text("Satznr: ${reiter.satznummer ?: "N/A"} | Lizenz: ${reiter.lizenz ?: "Keine"}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ConfirmStep(viewModel: ProfileOnboardingViewModel) {
|
||||
val state = viewModel.state
|
||||
val reiter = state.selectedReiter ?: return
|
||||
|
||||
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
Text("Daten bestätigen", style = MaterialTheme.typography.titleLarge)
|
||||
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant)
|
||||
) {
|
||||
Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Text("Vorname: ${reiter.vorname}")
|
||||
Text("Nachname: ${reiter.nachname}")
|
||||
Text("Satznummer: ${reiter.satznummer ?: "N/A"}")
|
||||
Text("Lizenz: ${reiter.lizenz ?: "Keine"}")
|
||||
Text("Klasse: ${reiter.lizenzKlasse}")
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
"Durch das Verknüpfen werden Ihre Aktionen in der App mit Ihrer offiziellen ZNS-Identität hinterlegt.",
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
|
||||
if (state.error != null) {
|
||||
Text(state.error, color = MaterialTheme.colorScheme.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FinishedStep(state: ProfileOnboardingState, onFinish: () -> Unit) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.CheckCircle,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(64.dp),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Text("Profil erfolgreich verknüpft!", style = MaterialTheme.typography.headlineSmall)
|
||||
Text("Willkommen, ${state.selectedReiter?.vorname ?: ""} ${state.selectedReiter?.nachname ?: ""}!")
|
||||
Spacer(Modifier.height(32.dp))
|
||||
Button(onClick = onFinish) {
|
||||
Text("Los geht's")
|
||||
}
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
package at.mocode.frontend.features.profile.presentation
|
||||
|
||||
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.core.domain.zns.ZnsImportProvider
|
||||
import at.mocode.frontend.core.domain.zns.ZnsRemoteReiter
|
||||
import at.mocode.frontend.features.profile.data.ProfileApiClient
|
||||
import at.mocode.frontend.features.profile.data.ProfileDto
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
enum class OnboardingStep {
|
||||
SEARCH_ZNS,
|
||||
CONFIRM_DATA,
|
||||
FINISHED
|
||||
}
|
||||
|
||||
data class ProfileOnboardingState(
|
||||
val currentStep: OnboardingStep = OnboardingStep.SEARCH_ZNS,
|
||||
val searchQuery: String = "",
|
||||
val searchResults: List<ZnsRemoteReiter> = emptyList(),
|
||||
val selectedReiter: ZnsRemoteReiter? = null,
|
||||
val isLoading: Boolean = false,
|
||||
val error: String? = null,
|
||||
val profile: ProfileDto? = null
|
||||
)
|
||||
|
||||
class ProfileOnboardingViewModel(
|
||||
private val znsImportProvider: ZnsImportProvider,
|
||||
private val profileApiClient: ProfileApiClient
|
||||
) : ViewModel() {
|
||||
|
||||
var state by mutableStateOf(ProfileOnboardingState())
|
||||
private set
|
||||
|
||||
fun onSearchQueryChange(query: String) {
|
||||
state = state.copy(searchQuery = query)
|
||||
if (query.length >= 3) {
|
||||
search()
|
||||
}
|
||||
}
|
||||
|
||||
private fun search() {
|
||||
viewModelScope.launch {
|
||||
state = state.copy(isLoading = true, error = null)
|
||||
try {
|
||||
znsImportProvider.searchRemote(state.searchQuery)
|
||||
state = state.copy(
|
||||
isLoading = false,
|
||||
searchResults = znsImportProvider.state.remoteReiter
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
state = state.copy(isLoading = false, error = "Fehler bei der ZNS-Suche: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun selectReiter(reiter: ZnsRemoteReiter) {
|
||||
state = state.copy(
|
||||
selectedReiter = reiter,
|
||||
currentStep = OnboardingStep.CONFIRM_DATA
|
||||
)
|
||||
}
|
||||
|
||||
fun confirmAndLink() {
|
||||
val reiter = state.selectedReiter ?: return
|
||||
viewModelScope.launch {
|
||||
state = state.copy(isLoading = true, error = null)
|
||||
try {
|
||||
val satznr = reiter.satznummer ?: ""
|
||||
val profile = profileApiClient.linkToZns(satznr)
|
||||
if (profile != null) {
|
||||
state = state.copy(
|
||||
isLoading = false,
|
||||
profile = profile,
|
||||
currentStep = OnboardingStep.FINISHED
|
||||
)
|
||||
} else {
|
||||
state = state.copy(isLoading = false, error = "Verknüpfung fehlgeschlagen.")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
state = state.copy(isLoading = false, error = "Fehler beim Verknüpfen: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun back() {
|
||||
state = state.copy(
|
||||
currentStep = when (state.currentStep) {
|
||||
OnboardingStep.SEARCH_ZNS -> OnboardingStep.SEARCH_ZNS
|
||||
OnboardingStep.CONFIRM_DATA -> OnboardingStep.SEARCH_ZNS
|
||||
OnboardingStep.FINISHED -> OnboardingStep.CONFIRM_DATA
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user