Fix #26: Vorname im Gruß + Tour-Verbesserungen + UI-Feinschliff
- TodayView: Begrüßung zeigt Vornamen aus UserProfileStore - TodayView: Leerer Zustand mit zwei CTA-Buttons (Moment / Todo) - TodayView: Untertitel auf "Lass uns mit der Beziehungspflege starten." geändert - Tour: Neue Schritte highlighten +Moment und +Todo in PersonDetailView - Tour: PeopleListView navigiert automatisch zum ersten Kontakt beim Tour-Schritt - Tour: App-Touren-Sektion in Einstellungen deaktiviert - Schriftgrößen: Alle Überschriften um 2pt verkleinert (34→32, etc.) - ContentView Preview: TourCoordinator-Environment ergänzt - Lokalisierung: Neue Strings für Gruß, Leerzustand und Tour Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -43,7 +43,7 @@ struct AppLockSetupView: View {
|
|||||||
|
|
||||||
VStack(spacing: 8) {
|
VStack(spacing: 8) {
|
||||||
Text(title)
|
Text(title)
|
||||||
.font(.system(size: 24, weight: .light, design: theme.displayDesign))
|
.font(.system(size: 22, weight: .light, design: theme.displayDesign))
|
||||||
.foregroundStyle(theme.contentPrimary)
|
.foregroundStyle(theme.contentPrimary)
|
||||||
Text(subtitle)
|
Text(subtitle)
|
||||||
.font(.system(size: 15))
|
.font(.system(size: 15))
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ struct AppLockView: View {
|
|||||||
// Title
|
// Title
|
||||||
VStack(spacing: 6) {
|
VStack(spacing: 6) {
|
||||||
Text("nahbar")
|
Text("nahbar")
|
||||||
.font(.system(size: 34, weight: .light, design: theme.displayDesign))
|
.font(.system(size: 32, weight: .light, design: theme.displayDesign))
|
||||||
.foregroundStyle(theme.contentPrimary)
|
.foregroundStyle(theme.contentPrimary)
|
||||||
Text("Code eingeben")
|
Text("Code eingeben")
|
||||||
.font(.system(size: 15))
|
.font(.system(size: 15))
|
||||||
@@ -166,7 +166,7 @@ struct PINPadView: View {
|
|||||||
} else {
|
} else {
|
||||||
Button { onKey(.digit(key.first!)) } label: {
|
Button { onKey(.digit(key.first!)) } label: {
|
||||||
Text(key)
|
Text(key)
|
||||||
.font(.system(size: 28, weight: .light, design: theme.displayDesign))
|
.font(.system(size: 26, weight: .light, design: theme.displayDesign))
|
||||||
.foregroundStyle(theme.contentPrimary)
|
.foregroundStyle(theme.contentPrimary)
|
||||||
.frame(width: 80, height: 80)
|
.frame(width: 80, height: 80)
|
||||||
.background(theme.surfaceCard)
|
.background(theme.surfaceCard)
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ struct CallWindowSetupView: View {
|
|||||||
.foregroundStyle(theme.accent)
|
.foregroundStyle(theme.accent)
|
||||||
|
|
||||||
Text("Gesprächszeit")
|
Text("Gesprächszeit")
|
||||||
.font(.system(size: 26, weight: .light, design: theme.displayDesign))
|
.font(.system(size: 24, weight: .light, design: theme.displayDesign))
|
||||||
.foregroundStyle(theme.contentPrimary)
|
.foregroundStyle(theme.contentPrimary)
|
||||||
|
|
||||||
Text("nahbar erinnert dich täglich in deinem Zeitfenster und schlägt einen Kontakt vor — mit Notizen, damit du vorbereitet bist.")
|
Text("nahbar erinnert dich täglich in deinem Zeitfenster und schlägt einen Kontakt vor — mit Notizen, damit du vorbereitet bist.")
|
||||||
|
|||||||
@@ -69,8 +69,6 @@ struct ContentView: View {
|
|||||||
nahbarOnboardingDone = true
|
nahbarOnboardingDone = true
|
||||||
showingNahbarOnboarding = false
|
showingNahbarOnboarding = false
|
||||||
checkCallWindow()
|
checkCallWindow()
|
||||||
// Tour nach Onboarding: Tab-Wechsel + Verzögerung nötig damit
|
|
||||||
// PeopleListView gerendert ist und anchorPreference-Frames gesammelt wurden.
|
|
||||||
scheduleTourIfNeeded()
|
scheduleTourIfNeeded()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -397,4 +395,5 @@ struct ContentView: View {
|
|||||||
.environmentObject(AppLockManager.shared)
|
.environmentObject(AppLockManager.shared)
|
||||||
.environmentObject(CloudSyncMonitor())
|
.environmentObject(CloudSyncMonitor())
|
||||||
.environmentObject(UserProfileStore.shared)
|
.environmentObject(UserProfileStore.shared)
|
||||||
|
.environment(TourCoordinator())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ struct IchView: View {
|
|||||||
VStack(alignment: .leading, spacing: 16) {
|
VStack(alignment: .leading, spacing: 16) {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Ich")
|
Text("Ich")
|
||||||
.font(.system(size: 34, weight: .light, design: theme.displayDesign))
|
.font(.system(size: 32, weight: .light, design: theme.displayDesign))
|
||||||
.foregroundStyle(theme.contentPrimary)
|
.foregroundStyle(theme.contentPrimary)
|
||||||
Spacer()
|
Spacer()
|
||||||
Button { showingEdit = true } label: {
|
Button { showingEdit = true } label: {
|
||||||
|
|||||||
@@ -230,6 +230,7 @@
|
|||||||
},
|
},
|
||||||
"%lld Schritte" : {
|
"%lld Schritte" : {
|
||||||
"comment" : "SettingsView – tour step count label (e.g. '6 Schritte')",
|
"comment" : "SettingsView – tour step count label (e.g. '6 Schritte')",
|
||||||
|
"extractionState" : "stale",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en" : {
|
"en" : {
|
||||||
"variations" : {
|
"variations" : {
|
||||||
@@ -917,6 +918,7 @@
|
|||||||
},
|
},
|
||||||
"App-Touren" : {
|
"App-Touren" : {
|
||||||
"comment" : "SettingsView – Tour section header",
|
"comment" : "SettingsView – Tour section header",
|
||||||
|
"extractionState" : "stale",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en" : {
|
"en" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
@@ -2771,35 +2773,38 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Guten Abend." : {
|
"Guten Abend" : {
|
||||||
"comment" : "TodayView – evening greeting",
|
"comment" : "TodayView – evening greeting (without punctuation, name appended in code)",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en" : {
|
"en" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
"value" : "Good evening."
|
"value" : "Good evening"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Guten Morgen" : {
|
||||||
|
"comment" : "TodayView – morning greeting (without punctuation, name appended in code)",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Good morning"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Guten Morgen." : {
|
"Guten Morgen." : {
|
||||||
"comment" : "TodayView – morning greeting",
|
|
||||||
"localizations" : {
|
|
||||||
"en" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "translated",
|
|
||||||
"value" : "Good morning."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"Guten Tag." : {
|
"Guten Tag" : {
|
||||||
"comment" : "TodayView – afternoon greeting",
|
"comment" : "TodayView – afternoon greeting (without punctuation, name appended in code)",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en" : {
|
"en" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
"value" : "Good afternoon."
|
"value" : "Good afternoon"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3372,8 +3377,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Lass uns mit der Beziehungspflege starten." : {
|
||||||
|
"comment" : "TodayView – empty state subtitle",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Let's start nurturing your relationships."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Leg Vorhaben an und erhalte eine Erinnerung – damit aus 'Wir müssen mal wieder…' ein echtes Treffen wird." : {
|
"Leg Vorhaben an und erhalte eine Erinnerung – damit aus 'Wir müssen mal wieder…' ein echtes Treffen wird." : {
|
||||||
"comment" : "TourCatalog – onboarding step 4 body",
|
"comment" : "TourCatalog – onboarding step 4 body",
|
||||||
|
"extractionState" : "stale",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en" : {
|
"en" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
@@ -3546,6 +3563,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Mit '+ Todo' planst du konkrete Aufgaben für diese Person – mit optionaler Erinnerung, damit nichts vergessen wird." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Mittel" : {
|
"Mittel" : {
|
||||||
"extractionState" : "stale",
|
"extractionState" : "stale",
|
||||||
@@ -4344,17 +4364,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Oder einer, der es noch wird." : {
|
|
||||||
"comment" : "TodayView – empty state subtitle",
|
|
||||||
"localizations" : {
|
|
||||||
"en" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "translated",
|
|
||||||
"value" : "Or one that will be."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Offen" : {
|
"Offen" : {
|
||||||
"extractionState" : "stale",
|
"extractionState" : "stale",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -5108,6 +5117,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Tippe auf '+ Moment', um Treffen oder Gespräche festzuhalten – so weißt du immer, worüber ihr das letzte Mal geredet habt." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Tippe auf + um jemanden hinzuzufügen." : {
|
"Tippe auf + um jemanden hinzuzufügen." : {
|
||||||
"comment" : "A description of how to add a new contact.",
|
"comment" : "A description of how to add a new contact.",
|
||||||
@@ -5210,6 +5222,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Todos anlegen" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Touch ID aktiviert" : {
|
"Touch ID aktiviert" : {
|
||||||
"comment" : "SettingsView – biometric label when Touch ID is active",
|
"comment" : "SettingsView – biometric label when Touch ID is active",
|
||||||
@@ -5235,6 +5250,7 @@
|
|||||||
},
|
},
|
||||||
"Tour starten" : {
|
"Tour starten" : {
|
||||||
"comment" : "SettingsView – button to replay a tour",
|
"comment" : "SettingsView – button to replay a tour",
|
||||||
|
"extractionState" : "stale",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en" : {
|
"en" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ struct PaywallView: View {
|
|||||||
.padding(.top, 24)
|
.padding(.top, 24)
|
||||||
|
|
||||||
Text("nahbar")
|
Text("nahbar")
|
||||||
.font(.system(size: 28, weight: .light, design: theme.displayDesign))
|
.font(.system(size: 26, weight: .light, design: theme.displayDesign))
|
||||||
.foregroundStyle(theme.contentPrimary)
|
.foregroundStyle(theme.contentPrimary)
|
||||||
|
|
||||||
Text("Wähle deinen Plan")
|
Text("Wähle deinen Plan")
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import SwiftData
|
|||||||
struct PeopleListView: View {
|
struct PeopleListView: View {
|
||||||
@Environment(\.nahbarTheme) var theme
|
@Environment(\.nahbarTheme) var theme
|
||||||
@Environment(\.modelContext) var modelContext
|
@Environment(\.modelContext) var modelContext
|
||||||
|
@Environment(TourCoordinator.self) private var tourCoordinator
|
||||||
@Query(sort: \Person.name) private var people: [Person]
|
@Query(sort: \Person.name) private var people: [Person]
|
||||||
@StateObject private var store = StoreManager.shared
|
@StateObject private var store = StoreManager.shared
|
||||||
|
|
||||||
@@ -34,7 +35,7 @@ struct PeopleListView: View {
|
|||||||
HStack(alignment: .firstTextBaseline) {
|
HStack(alignment: .firstTextBaseline) {
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
Text("Menschen")
|
Text("Menschen")
|
||||||
.font(.system(size: 34, weight: .light, design: theme.displayDesign))
|
.font(.system(size: 32, weight: .light, design: theme.displayDesign))
|
||||||
.foregroundStyle(theme.contentPrimary)
|
.foregroundStyle(theme.contentPrimary)
|
||||||
// Kontaktlimit-Hinweis für Free-Nutzer
|
// Kontaktlimit-Hinweis für Free-Nutzer
|
||||||
if !store.isPro {
|
if !store.isPro {
|
||||||
@@ -149,6 +150,14 @@ struct PeopleListView: View {
|
|||||||
.sheet(isPresented: $showingPaywall) {
|
.sheet(isPresented: $showingPaywall) {
|
||||||
PaywallView(targeting: .pro)
|
PaywallView(targeting: .pro)
|
||||||
}
|
}
|
||||||
|
// Automatisch zum ersten Kontakt navigieren, wenn die Tour
|
||||||
|
// den +Moment- oder +Todo-Button spotlighten möchte.
|
||||||
|
.onChange(of: tourCoordinator.currentStep?.target) { _, target in
|
||||||
|
guard target == .addMomentButton || target == .addTodoButton else { return }
|
||||||
|
if selectedPerson == nil, let first = filteredPeople.first {
|
||||||
|
selectedPerson = first
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var emptyState: some View {
|
private var emptyState: some View {
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ struct PersonDetailView: View {
|
|||||||
|
|
||||||
VStack(alignment: .leading, spacing: 5) {
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
Text(person.name)
|
Text(person.name)
|
||||||
.font(.system(size: 26, weight: .light, design: theme.displayDesign))
|
.font(.system(size: 24, weight: .light, design: theme.displayDesign))
|
||||||
.foregroundStyle(theme.contentPrimary)
|
.foregroundStyle(theme.contentPrimary)
|
||||||
|
|
||||||
TagBadge(text: person.tag.rawValue)
|
TagBadge(text: person.tag.rawValue)
|
||||||
@@ -180,6 +180,7 @@ struct PersonDetailView: View {
|
|||||||
.background(theme.accent.opacity(0.10))
|
.background(theme.accent.opacity(0.10))
|
||||||
.clipShape(Capsule())
|
.clipShape(Capsule())
|
||||||
}
|
}
|
||||||
|
.tourTarget(.addMomentButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Persönlichkeitsbasierte Vorhaben-Vorschläge (ersetzt nextStepSection)
|
// Persönlichkeitsbasierte Vorhaben-Vorschläge (ersetzt nextStepSection)
|
||||||
@@ -465,6 +466,7 @@ struct PersonDetailView: View {
|
|||||||
.background(theme.accent.opacity(0.10))
|
.background(theme.accent.opacity(0.10))
|
||||||
.clipShape(Capsule())
|
.clipShape(Capsule())
|
||||||
}
|
}
|
||||||
|
.tourTarget(.addTodoButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
if visibleTodos.isEmpty {
|
if visibleTodos.isEmpty {
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ struct SettingsView: View {
|
|||||||
|
|
||||||
// Header
|
// Header
|
||||||
Text("Einstellungen")
|
Text("Einstellungen")
|
||||||
.font(.system(size: 34, weight: .light, design: theme.displayDesign))
|
.font(.system(size: 32, weight: .light, design: theme.displayDesign))
|
||||||
.foregroundStyle(theme.contentPrimary)
|
.foregroundStyle(theme.contentPrimary)
|
||||||
.padding(.horizontal, 20)
|
.padding(.horizontal, 20)
|
||||||
.padding(.top, 12)
|
.padding(.top, 12)
|
||||||
@@ -591,42 +591,7 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// App-Touren
|
// App-Touren (deaktiviert)
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
|
||||||
SectionHeader(title: "App-Touren", icon: "sparkles")
|
|
||||||
.padding(.horizontal, 20)
|
|
||||||
|
|
||||||
VStack(spacing: 0) {
|
|
||||||
ForEach(Array(TourCatalog.all.enumerated()), id: \.element.id) { index, tour in
|
|
||||||
if index > 0 { RowDivider() }
|
|
||||||
HStack(spacing: 14) {
|
|
||||||
Image(systemName: "sparkles")
|
|
||||||
.font(.system(size: 15))
|
|
||||||
.foregroundStyle(theme.contentTertiary)
|
|
||||||
.frame(width: 22)
|
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
|
||||||
Text(tour.title)
|
|
||||||
.font(.system(size: 15))
|
|
||||||
.foregroundStyle(theme.contentPrimary)
|
|
||||||
Text(String.localizedStringWithFormat(String(localized: "%lld Schritte"), Int64(tour.steps.count)))
|
|
||||||
.font(.system(size: 12))
|
|
||||||
.foregroundStyle(theme.contentTertiary)
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
Button("Tour starten") {
|
|
||||||
tourCoordinator.start(tour.id)
|
|
||||||
}
|
|
||||||
.font(.system(size: 13, weight: .medium))
|
|
||||||
.foregroundStyle(theme.accent)
|
|
||||||
}
|
|
||||||
.padding(.horizontal, 16)
|
|
||||||
.padding(.vertical, 12)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.background(theme.surfaceCard)
|
|
||||||
.clipShape(RoundedRectangle(cornerRadius: theme.radiusCard))
|
|
||||||
.padding(.horizontal, 20)
|
|
||||||
}
|
|
||||||
|
|
||||||
// About
|
// About
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import UserNotifications
|
|||||||
struct TodayView: View {
|
struct TodayView: View {
|
||||||
@Environment(\.nahbarTheme) var theme
|
@Environment(\.nahbarTheme) var theme
|
||||||
@Environment(\.modelContext) private var modelContext
|
@Environment(\.modelContext) private var modelContext
|
||||||
|
@EnvironmentObject private var profileStore: UserProfileStore
|
||||||
@Query private var people: [Person]
|
@Query private var people: [Person]
|
||||||
// V5: Nachwirkungen sind jetzt Treffen-Momente mit Status "warte_nachwirkung"
|
// V5: Nachwirkungen sind jetzt Treffen-Momente mit Status "warte_nachwirkung"
|
||||||
@Query(filter: #Predicate<Moment> {
|
@Query(filter: #Predicate<Moment> {
|
||||||
@@ -102,11 +103,16 @@ struct TodayView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var greeting: LocalizedStringKey {
|
private var greeting: String {
|
||||||
let hour = Calendar.current.component(.hour, from: Date())
|
let hour = Calendar.current.component(.hour, from: Date())
|
||||||
if hour < 12 { return "Guten Morgen." }
|
let base: String
|
||||||
if hour < 18 { return "Guten Tag." }
|
if hour < 12 { base = String(localized: "Guten Morgen") }
|
||||||
return "Guten Abend."
|
else if hour < 18 { base = String(localized: "Guten Tag") }
|
||||||
|
else { base = String(localized: "Guten Abend") }
|
||||||
|
|
||||||
|
let firstName = profileStore.name.split(separator: " ").first.map(String.init) ?? ""
|
||||||
|
if firstName.isEmpty { return "\(base)." }
|
||||||
|
return "\(base), \(firstName)."
|
||||||
}
|
}
|
||||||
|
|
||||||
private var formattedToday: String {
|
private var formattedToday: String {
|
||||||
@@ -120,7 +126,7 @@ struct TodayView: View {
|
|||||||
// Header
|
// Header
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
Text(greeting)
|
Text(greeting)
|
||||||
.font(.system(size: 34, weight: .light, design: theme.displayDesign))
|
.font(.system(size: 32, weight: .light, design: theme.displayDesign))
|
||||||
.foregroundStyle(theme.contentPrimary)
|
.foregroundStyle(theme.contentPrimary)
|
||||||
Text(formattedToday)
|
Text(formattedToday)
|
||||||
.font(.system(size: 15, design: theme.displayDesign))
|
.font(.system(size: 15, design: theme.displayDesign))
|
||||||
@@ -302,7 +308,7 @@ struct TodayView: View {
|
|||||||
Text("Ein ruhiger Tag.")
|
Text("Ein ruhiger Tag.")
|
||||||
.font(.system(size: 20, weight: .light, design: theme.displayDesign))
|
.font(.system(size: 20, weight: .light, design: theme.displayDesign))
|
||||||
.foregroundStyle(theme.contentPrimary)
|
.foregroundStyle(theme.contentPrimary)
|
||||||
Text("Oder einer, der es noch wird.")
|
Text("Lass uns mit der Beziehungspflege starten.")
|
||||||
.font(.system(size: 15))
|
.font(.system(size: 15))
|
||||||
.foregroundStyle(theme.contentTertiary)
|
.foregroundStyle(theme.contentTertiary)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ struct Tour: Identifiable, Hashable {
|
|||||||
triggerMode: TriggerMode
|
triggerMode: TriggerMode
|
||||||
) {
|
) {
|
||||||
precondition(!steps.isEmpty, "A tour must have at least 1 step.")
|
precondition(!steps.isEmpty, "A tour must have at least 1 step.")
|
||||||
precondition(steps.count <= 6, "A tour must not exceed 6 steps. Got \(steps.count).")
|
precondition(steps.count <= 8, "A tour must not exceed 8 steps. Got \(steps.count).")
|
||||||
self.id = id
|
self.id = id
|
||||||
self.title = title
|
self.title = title
|
||||||
self.steps = steps
|
self.steps = steps
|
||||||
|
|||||||
@@ -33,9 +33,15 @@ enum TourCatalog {
|
|||||||
),
|
),
|
||||||
TourStep(
|
TourStep(
|
||||||
title: "Plane das Nächste",
|
title: "Plane das Nächste",
|
||||||
body: "Leg Vorhaben an und erhalte eine Erinnerung – damit aus 'Wir müssen mal wieder…' ein echtes Treffen wird.",
|
body: "Tippe auf '+ Moment', um Treffen oder Gespräche festzuhalten – so weißt du immer, worüber ihr das letzte Mal geredet habt.",
|
||||||
target: nil,
|
target: .addMomentButton,
|
||||||
preferredCardPosition: .center
|
preferredCardPosition: .below
|
||||||
|
),
|
||||||
|
TourStep(
|
||||||
|
title: "Todos anlegen",
|
||||||
|
body: "Mit '+ Todo' planst du konkrete Aufgaben für diese Person – mit optionaler Erinnerung, damit nichts vergessen wird.",
|
||||||
|
target: .addTodoButton,
|
||||||
|
preferredCardPosition: .below
|
||||||
),
|
),
|
||||||
TourStep(
|
TourStep(
|
||||||
title: "Sanfte Erinnerungen",
|
title: "Sanfte Erinnerungen",
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ enum TourTargetID: String, Hashable, CaseIterable {
|
|||||||
case filterChips
|
case filterChips
|
||||||
case relationshipStrengthBadge
|
case relationshipStrengthBadge
|
||||||
case contactCardFirst
|
case contactCardFirst
|
||||||
|
case addMomentButton
|
||||||
|
case addTodoButton
|
||||||
case personalityTab
|
case personalityTab
|
||||||
case insightsTab
|
case insightsTab
|
||||||
case settingsEntry
|
case settingsEntry
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ import Testing
|
|||||||
@Suite("TourCatalog – Validierung")
|
@Suite("TourCatalog – Validierung")
|
||||||
struct TourCatalogTests {
|
struct TourCatalogTests {
|
||||||
|
|
||||||
@Test("Alle Touren haben mindestens 1 und höchstens 6 Steps")
|
@Test("Alle Touren haben mindestens 1 und höchstens 7 Steps")
|
||||||
func allToursHaveValidStepCount() {
|
func allToursHaveValidStepCount() {
|
||||||
for tour in TourCatalog.all {
|
for tour in TourCatalog.all {
|
||||||
#expect(!tour.steps.isEmpty, "Tour \(tour.id.rawValue) hat keine Steps")
|
#expect(!tour.steps.isEmpty, "Tour \(tour.id.rawValue) hat keine Steps")
|
||||||
#expect(tour.steps.count <= 6, "Tour \(tour.id.rawValue) hat mehr als 6 Steps")
|
#expect(tour.steps.count <= 7, "Tour \(tour.id.rawValue) hat mehr als 7 Steps")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,9 +25,9 @@ struct TourCatalogTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Onboarding-Tour hat genau 6 Steps")
|
@Test("Onboarding-Tour hat genau 7 Steps")
|
||||||
func onboardingTourHasSixSteps() {
|
func onboardingTourHasSevenSteps() {
|
||||||
#expect(TourCatalog.onboarding.steps.count == 6)
|
#expect(TourCatalog.onboarding.steps.count == 7)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Onboarding-Tour hat triggerMode .manualOrFirstLaunch")
|
@Test("Onboarding-Tour hat triggerMode .manualOrFirstLaunch")
|
||||||
|
|||||||
Reference in New Issue
Block a user