Nudge hinzugefügt, Navigation optimiert.

This commit is contained in:
2026-04-19 16:17:07 +02:00
parent a776992f0c
commit 67cfc95265
6 changed files with 109 additions and 6 deletions
+28 -2
View File
@@ -189,8 +189,34 @@ struct IchView: View {
}
// Vorlieben
if !profileStore.likes.isEmpty || !profileStore.dislikes.isEmpty {
SectionHeader(title: "Vorlieben", icon: "heart")
SectionHeader(title: "Vorlieben", icon: "heart")
if profileStore.likes.isEmpty && profileStore.dislikes.isEmpty {
// Nudge: fehlende Vorlieben
Button { showingEdit = true } label: {
HStack(spacing: 12) {
Image(systemName: "sparkles")
.font(.system(size: 14))
.foregroundStyle(theme.accent)
Text("Uns fehlt noch was wir würden gerne mehr von dir erfahren.")
.font(.system(size: 14))
.foregroundStyle(theme.contentSecondary)
.multilineTextAlignment(.leading)
Spacer()
Image(systemName: "chevron.right")
.font(.system(size: 11, weight: .medium))
.foregroundStyle(theme.contentTertiary)
}
.padding(.horizontal, 16)
.padding(.vertical, 14)
.background(theme.accent.opacity(0.06))
.clipShape(RoundedRectangle(cornerRadius: theme.radiusCard))
.overlay(
RoundedRectangle(cornerRadius: theme.radiusCard)
.strokeBorder(theme.accent.opacity(0.18), lineWidth: 1)
)
}
.buttonStyle(.plain)
} else {
VStack(spacing: 0) {
if !profileStore.likes.isEmpty {
preferenceRow(label: "Mag ich", text: profileStore.likes, color: .green)
+3
View File
@@ -3979,6 +3979,9 @@
}
}
}
},
"Uns fehlt noch was wir würden gerne mehr von dir erfahren." : {
},
"Unwohl" : {
"comment" : "RatingQuestion negative pole for comfort during meeting",
+8 -1
View File
@@ -11,6 +11,7 @@ struct PeopleListView: View {
@State private var selectedTag: PersonTag? = nil
@State private var showingAddPerson = false
@State private var showingPaywall = false
@State private var selectedPerson: Person? = nil
private let freeContactLimit = 3
@@ -112,8 +113,11 @@ struct PeopleListView: View {
ScrollView {
VStack(spacing: 0) {
ForEach(Array(filteredPeople.enumerated()), id: \.element.id) { index, person in
NavigationLink(destination: PersonDetailView(person: person)) {
Button {
selectedPerson = person
} label: {
PersonRowView(person: person)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
if index < filteredPeople.count - 1 {
@@ -132,6 +136,9 @@ struct PeopleListView: View {
}
.background(theme.backgroundPrimary.ignoresSafeArea())
.navigationBarHidden(true)
.navigationDestination(item: $selectedPerson) { person in
PersonDetailView(person: person)
}
}
.sheet(isPresented: $showingAddPerson) {
AddPersonView()
+11 -3
View File
@@ -590,11 +590,14 @@ private struct DeletableMomentRow: View {
}
.background(theme.surfaceCard)
.offset(x: offset)
.gesture(
DragGesture(minimumDistance: 10, coordinateSpace: .local)
// simultaneousGesture erlaubt dem übergeordneten ScrollView weiterhin zu scrollen.
// Der Winkeltest (Faktor 2.5) lässt nur klar horizontale Gesten durch.
.simultaneousGesture(
DragGesture(minimumDistance: 20, coordinateSpace: .local)
.onChanged { value in
let x = value.translation.width
guard abs(x) > abs(value.translation.height) * 0.6 else { return }
let y = value.translation.height
guard abs(x) > abs(y) * 2.5 else { return }
if x > 0 {
offset = min(x, actionWidth + 16)
} else {
@@ -603,6 +606,11 @@ private struct DeletableMomentRow: View {
}
.onEnded { value in
let x = value.translation.width
let y = value.translation.height
guard abs(x) > abs(y) * 2.5 else {
withAnimation(.spring(response: 0.32, dampingFraction: 0.8)) { offset = 0 }
return
}
if x > actionWidth + 20 {
// Vollständiges Rechts-Wischen: sofort togglen, zurückspringen
onToggleImportant()
@@ -185,3 +185,62 @@ struct UserProfileStoreNewFieldsTests {
#expect(Set(options).count == options.count)
}
}
// MARK: - Vorlieben-Nudge Anzeigelogik
@Suite("IchView Vorlieben-Nudge Sichtbarkeit")
struct VorliebenNudgeTests {
// Spiegelt die Bedingung in IchView.infoSection wider:
// showNudge = likes.isEmpty && dislikes.isEmpty
// showContent = !likes.isEmpty || !dislikes.isEmpty
// infoSection nur sichtbar wenn !isEmpty (getrennt getestet)
private func showNudge(likes: String, dislikes: String) -> Bool {
likes.isEmpty && dislikes.isEmpty
}
@Test("Beide leer → Nudge wird angezeigt")
func bothEmptyShowsNudge() {
#expect(showNudge(likes: "", dislikes: ""))
}
@Test("Nur likes gesetzt → kein Nudge")
func onlyLikesSetHidesNudge() {
#expect(!showNudge(likes: "Kaffee", dislikes: ""))
}
@Test("Nur dislikes gesetzt → kein Nudge")
func onlyDislikesSetHidesNudge() {
#expect(!showNudge(likes: "", dislikes: "Lärm"))
}
@Test("Beide gesetzt → kein Nudge")
func bothSetHidesNudge() {
#expect(!showNudge(likes: "Kaffee", dislikes: "Lärm"))
}
@Test("Nudge und Content schließen sich gegenseitig aus")
func nudgeAndContentAreMutuallyExclusive() {
let cases: [(likes: String, dislikes: String)] = [
("", ""),
("Kaffee", ""),
("", "Lärm"),
("Kaffee", "Lärm"),
]
for c in cases {
let nudge = showNudge(likes: c.likes, dislikes: c.dislikes)
let content = !c.likes.isEmpty || !c.dislikes.isEmpty
// Genau einer von beiden ist wahr, nie beide gleichzeitig
#expect(nudge != content || (nudge == false && content == false),
"likes='\(c.likes)' dislikes='\(c.dislikes)': nudge=\(nudge) content=\(content)")
}
}
@Test("Whitespace-only likes gilt als leer → Nudge erscheint (Store trimmt beim Speichern)")
func whitespaceOnlyLikesCountsAsEmpty() {
// Der Store trimmt Eingaben beim Speichern, daher landet nie reines Whitespace
let trimmed = " ".trimmingCharacters(in: .whitespaces)
#expect(showNudge(likes: trimmed, dislikes: ""))
}
}