Nudge hinzugefügt, Navigation optimiert.
This commit is contained in:
BIN
Binary file not shown.
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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: ""))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user