Nudge hinzugefügt, Navigation optimiert.
This commit is contained in:
BIN
Binary file not shown.
@@ -189,8 +189,34 @@ struct IchView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Vorlieben
|
// 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) {
|
VStack(spacing: 0) {
|
||||||
if !profileStore.likes.isEmpty {
|
if !profileStore.likes.isEmpty {
|
||||||
preferenceRow(label: "Mag ich", text: profileStore.likes, color: .green)
|
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" : {
|
"Unwohl" : {
|
||||||
"comment" : "RatingQuestion – negative pole for comfort during meeting",
|
"comment" : "RatingQuestion – negative pole for comfort during meeting",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ struct PeopleListView: View {
|
|||||||
@State private var selectedTag: PersonTag? = nil
|
@State private var selectedTag: PersonTag? = nil
|
||||||
@State private var showingAddPerson = false
|
@State private var showingAddPerson = false
|
||||||
@State private var showingPaywall = false
|
@State private var showingPaywall = false
|
||||||
|
@State private var selectedPerson: Person? = nil
|
||||||
|
|
||||||
private let freeContactLimit = 3
|
private let freeContactLimit = 3
|
||||||
|
|
||||||
@@ -112,8 +113,11 @@ struct PeopleListView: View {
|
|||||||
ScrollView {
|
ScrollView {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
ForEach(Array(filteredPeople.enumerated()), id: \.element.id) { index, person in
|
ForEach(Array(filteredPeople.enumerated()), id: \.element.id) { index, person in
|
||||||
NavigationLink(destination: PersonDetailView(person: person)) {
|
Button {
|
||||||
|
selectedPerson = person
|
||||||
|
} label: {
|
||||||
PersonRowView(person: person)
|
PersonRowView(person: person)
|
||||||
|
.contentShape(Rectangle())
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
if index < filteredPeople.count - 1 {
|
if index < filteredPeople.count - 1 {
|
||||||
@@ -132,6 +136,9 @@ struct PeopleListView: View {
|
|||||||
}
|
}
|
||||||
.background(theme.backgroundPrimary.ignoresSafeArea())
|
.background(theme.backgroundPrimary.ignoresSafeArea())
|
||||||
.navigationBarHidden(true)
|
.navigationBarHidden(true)
|
||||||
|
.navigationDestination(item: $selectedPerson) { person in
|
||||||
|
PersonDetailView(person: person)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $showingAddPerson) {
|
.sheet(isPresented: $showingAddPerson) {
|
||||||
AddPersonView()
|
AddPersonView()
|
||||||
|
|||||||
@@ -590,11 +590,14 @@ private struct DeletableMomentRow: View {
|
|||||||
}
|
}
|
||||||
.background(theme.surfaceCard)
|
.background(theme.surfaceCard)
|
||||||
.offset(x: offset)
|
.offset(x: offset)
|
||||||
.gesture(
|
// simultaneousGesture erlaubt dem übergeordneten ScrollView weiterhin zu scrollen.
|
||||||
DragGesture(minimumDistance: 10, coordinateSpace: .local)
|
// Der Winkeltest (Faktor 2.5) lässt nur klar horizontale Gesten durch.
|
||||||
|
.simultaneousGesture(
|
||||||
|
DragGesture(minimumDistance: 20, coordinateSpace: .local)
|
||||||
.onChanged { value in
|
.onChanged { value in
|
||||||
let x = value.translation.width
|
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 {
|
if x > 0 {
|
||||||
offset = min(x, actionWidth + 16)
|
offset = min(x, actionWidth + 16)
|
||||||
} else {
|
} else {
|
||||||
@@ -603,6 +606,11 @@ private struct DeletableMomentRow: View {
|
|||||||
}
|
}
|
||||||
.onEnded { value in
|
.onEnded { value in
|
||||||
let x = value.translation.width
|
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 {
|
if x > actionWidth + 20 {
|
||||||
// Vollständiges Rechts-Wischen: sofort togglen, zurückspringen
|
// Vollständiges Rechts-Wischen: sofort togglen, zurückspringen
|
||||||
onToggleImportant()
|
onToggleImportant()
|
||||||
|
|||||||
@@ -185,3 +185,62 @@ struct UserProfileStoreNewFieldsTests {
|
|||||||
#expect(Set(options).count == options.count)
|
#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