feat: füge DraftStore und Speichern/Resume von Wizard-Status hinzu
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
This commit is contained in:
+15
@@ -0,0 +1,15 @@
|
||||
package at.mocode.frontend.core.wizard.draft
|
||||
|
||||
/**
|
||||
* Minimaler DraftStore (MVP): in‑Memory, versioniert per Key. Persistenz folgt später.
|
||||
*/
|
||||
object DraftStoreMemory {
|
||||
private data class Entry(val version: Int, val stepId: String)
|
||||
private val store = mutableMapOf<String, Entry>()
|
||||
|
||||
fun save(flowKey: String, version: Int, stepId: String) {
|
||||
store[flowKey] = Entry(version, stepId)
|
||||
}
|
||||
|
||||
fun load(flowKey: String): Pair<Int, String>? = store[flowKey]?.let { it.version to it.stepId }
|
||||
}
|
||||
+5
-3
@@ -21,9 +21,11 @@ data class DemoEventAcc(
|
||||
|
||||
object DemoEventGuards {
|
||||
val hasZns: Guard<DemoEventStep, DemoEventAcc> = { ctx, _ -> (ctx.stats?.vereinCount ?: 0) > 0 }
|
||||
// Platzhalter-Guard: aktuell stets true, damit Verhalten dem Legacy-Pfad entspricht.
|
||||
// Wird später durch echte Domänenlogik ersetzt (Veranstalter-Typ/ID etc.).
|
||||
val needsContactPerson: Guard<DemoEventStep, DemoEventAcc> = { _, _ -> true }
|
||||
// Heuristik für Demo: Kontaktperson nötig, wenn keine Veranstalter-ID vorhanden
|
||||
// oder die Nummer ein Organisations-Präfix trägt (z. B. „ORG-“)
|
||||
val needsContactPerson: Guard<DemoEventStep, DemoEventAcc> = { _, acc ->
|
||||
acc.veranstalterId == null || acc.veranstalterNr.startsWith("ORG-")
|
||||
}
|
||||
}
|
||||
|
||||
val DemoEventFlow = flow<DemoEventStep, DemoEventAcc>(start = DemoEventStep.ZnsCheck) {
|
||||
|
||||
+40
-13
@@ -1,6 +1,9 @@
|
||||
package at.mocode.frontend.core.wizard.ui
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.key.*
|
||||
|
||||
/**
|
||||
* Desktop-spezifische Variante. Hotkeys werden in einem Folge‑Inkrement ergänzt,
|
||||
@@ -20,17 +23,41 @@ fun WizardScaffoldWithHotkeys(
|
||||
finishLabel: String = "Fertig",
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
WizardScaffold(
|
||||
steps = steps,
|
||||
currentIndex = currentIndex,
|
||||
canBack = canBack,
|
||||
canNext = canNext,
|
||||
onBack = onBack,
|
||||
onNext = onNext,
|
||||
onSaveDraft = onSaveDraft,
|
||||
nextLabel = nextLabel,
|
||||
backLabel = backLabel,
|
||||
finishLabel = finishLabel,
|
||||
content = content
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier.onPreviewKeyEvent { evt ->
|
||||
if (evt.type != KeyEventType.KeyUp) return@onPreviewKeyEvent false
|
||||
when {
|
||||
// Alt+S: Draft speichern (falls vorhanden)
|
||||
evt.isAltPressed && evt.key == Key.S -> {
|
||||
onSaveDraft?.invoke()
|
||||
true
|
||||
}
|
||||
// Shift+Enter: Zurück
|
||||
evt.isShiftPressed && evt.key == Key.Enter -> {
|
||||
if (canBack) onBack()
|
||||
true
|
||||
}
|
||||
// Enter: Weiter/Fertig
|
||||
evt.key == Key.Enter -> {
|
||||
if (canNext) onNext()
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
) {
|
||||
WizardScaffold(
|
||||
steps = steps,
|
||||
currentIndex = currentIndex,
|
||||
canBack = canBack,
|
||||
canNext = canNext,
|
||||
onBack = onBack,
|
||||
onNext = onNext,
|
||||
onSaveDraft = onSaveDraft,
|
||||
nextLabel = nextLabel,
|
||||
backLabel = backLabel,
|
||||
finishLabel = finishLabel,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+44
@@ -3,6 +3,7 @@ package at.mocode.frontend.core.wizard
|
||||
import at.mocode.frontend.core.domain.repository.MasterdataStats
|
||||
import at.mocode.frontend.core.navigation.AppScreen
|
||||
import at.mocode.frontend.core.wizard.runtime.WizardContext
|
||||
import at.mocode.frontend.core.wizard.samples.DemoEventAcc
|
||||
import at.mocode.frontend.core.wizard.samples.DemoEventFlow
|
||||
import at.mocode.frontend.core.wizard.samples.DemoEventStep
|
||||
import at.mocode.frontend.core.wizard.samples.demoStartState
|
||||
@@ -47,4 +48,47 @@ class WizardRuntimeJvmTest {
|
||||
val s2 = DemoEventFlow.back(s1)
|
||||
assertEquals(DemoEventStep.ZnsCheck, s2.current)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun selection_goes_to_ansprechperson_when_guard_true_by_default() {
|
||||
val ctx = WizardContext(
|
||||
origin = AppScreen.Home,
|
||||
isOnline = true,
|
||||
stats = MasterdataStats(
|
||||
lastImport = null,
|
||||
vereinCount = 1,
|
||||
reiterCount = 0,
|
||||
pferdCount = 0,
|
||||
funktionaerCount = 0
|
||||
)
|
||||
)
|
||||
val s0 = demoStartState(AppScreen.Home)
|
||||
val s1 = DemoEventFlow.next(ctx, s0) // -> VeranstalterSelection
|
||||
val s2 = DemoEventFlow.next(ctx, s1)
|
||||
assertEquals(DemoEventStep.AnsprechpersonMapping, s2.current)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun selection_goes_to_meta_when_guard_false_by_acc() {
|
||||
val ctx = WizardContext(
|
||||
origin = AppScreen.Home,
|
||||
isOnline = true,
|
||||
stats = MasterdataStats(
|
||||
lastImport = null,
|
||||
vereinCount = 1,
|
||||
reiterCount = 0,
|
||||
pferdCount = 0,
|
||||
funktionaerCount = 0
|
||||
)
|
||||
)
|
||||
// Manuell einen State anlegen, der bereits auf VeranstalterSelection steht
|
||||
val acc = DemoEventAcc(veranstalterId = "id-1", veranstalterNr = "V-123")
|
||||
val s1 = at.mocode.frontend.core.wizard.runtime.WizardState(
|
||||
current = DemoEventStep.VeranstalterSelection,
|
||||
history = listOf(DemoEventStep.ZnsCheck),
|
||||
acc = acc
|
||||
)
|
||||
val s2 = DemoEventFlow.next(ctx, s1)
|
||||
assertEquals(DemoEventStep.MetaData, s2.current)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user