From 53812e4924f48f9f2aaf8f48e5560bedf2759169 Mon Sep 17 00:00:00 2001 From: Sven Date: Sun, 19 Apr 2026 19:54:41 +0200 Subject: [PATCH] Umbau aud "Momante" --- nahbar/nahbar.xcodeproj/project.pbxproj | 4 - .../UserInterfaceState.xcuserstate | Bin 74581 -> 94432 bytes nahbar/nahbar/AddMomentView.swift | 3 + nahbar/nahbar/CallSuggestionView.swift | 15 +- nahbar/nahbar/ContentView.swift | 112 ++++++++++++++ nahbar/nahbar/Localizable.xcstrings | 145 +++++++++++++++++- nahbar/nahbar/LogbuchView.swift | 57 +++++-- nahbar/nahbar/Models.swift | 113 +++++++++++++- nahbar/nahbar/NahbarMigration.swift | 125 ++++++++++++++- nahbar/nahbar/OnboardingContainerView.swift | 8 +- nahbar/nahbar/PersonDetailView.swift | 3 +- nahbar/nahbar/PersonalityEngine.swift | 12 +- nahbar/nahbar/SettingsView.swift | 4 +- nahbar/nahbar/TodayView.swift | 133 ++++++++++------ nahbar/nahbarTests/AppEventLogTests.swift | 12 +- nahbar/nahbarTests/ModelTests.swift | 73 ++++++++- .../nahbarTests/NahbarPersonalityTests.swift | 2 +- nahbar/nahbarTests/VisitRatingTests.swift | 31 ++-- 18 files changed, 733 insertions(+), 119 deletions(-) diff --git a/nahbar/nahbar.xcodeproj/project.pbxproj b/nahbar/nahbar.xcodeproj/project.pbxproj index 2d7a413..d993050 100644 --- a/nahbar/nahbar.xcodeproj/project.pbxproj +++ b/nahbar/nahbar.xcodeproj/project.pbxproj @@ -19,7 +19,6 @@ 26B2CAE72F93C03F0039BA3B /* VisitRatingFlowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26B2CAE62F93C03F0039BA3B /* VisitRatingFlowView.swift */; }; 26B2CAE92F93C0490039BA3B /* AftermathRatingFlowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26B2CAE82F93C0490039BA3B /* AftermathRatingFlowView.swift */; }; 26B2CAEB2F93C05A0039BA3B /* VisitSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26B2CAEA2F93C05A0039BA3B /* VisitSummaryView.swift */; }; - 26B2CAED2F93C0680039BA3B /* VisitHistorySection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26B2CAEC2F93C0680039BA3B /* VisitHistorySection.swift */; }; 26B2CAF12F93C52C0039BA3B /* VisitEditFlowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26B2CAF02F93C52C0039BA3B /* VisitEditFlowView.swift */; }; 26B2CAF72F93ED690039BA3B /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 26B2CAF62F93ED690039BA3B /* Localizable.xcstrings */; }; 26B9930C2F94B32800E9B16C /* PrivacyBadgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26B9930B2F94B32800E9B16C /* PrivacyBadgeView.swift */; }; @@ -112,7 +111,6 @@ 26B2CAE62F93C03F0039BA3B /* VisitRatingFlowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisitRatingFlowView.swift; sourceTree = ""; }; 26B2CAE82F93C0490039BA3B /* AftermathRatingFlowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AftermathRatingFlowView.swift; sourceTree = ""; }; 26B2CAEA2F93C05A0039BA3B /* VisitSummaryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisitSummaryView.swift; sourceTree = ""; }; - 26B2CAEC2F93C0680039BA3B /* VisitHistorySection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisitHistorySection.swift; sourceTree = ""; }; 26B2CAF02F93C52C0039BA3B /* VisitEditFlowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisitEditFlowView.swift; sourceTree = ""; }; 26B2CAF62F93ED690039BA3B /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; 26B9930B2F94B32800E9B16C /* PrivacyBadgeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyBadgeView.swift; sourceTree = ""; }; @@ -225,7 +223,6 @@ 26B2CAE62F93C03F0039BA3B /* VisitRatingFlowView.swift */, 26B2CAE82F93C0490039BA3B /* AftermathRatingFlowView.swift */, 26B2CAEA2F93C05A0039BA3B /* VisitSummaryView.swift */, - 26B2CAEC2F93C0680039BA3B /* VisitHistorySection.swift */, 26B2CAF02F93C52C0039BA3B /* VisitEditFlowView.swift */, ); sourceTree = ""; @@ -440,7 +437,6 @@ 269ECE662F92B5C700444B14 /* NahbarMigration.swift in Sources */, 26EF66322F9112E700824F91 /* Models.swift in Sources */, 26F8B0CF2F94E7B1004905B9 /* PersonalityQuizView.swift in Sources */, - 26B2CAED2F93C0680039BA3B /* VisitHistorySection.swift in Sources */, 26EF66332F9112E700824F91 /* TodayView.swift in Sources */, 26EF66412F9129F000824F91 /* CallWindowSetupView.swift in Sources */, 26F8B0CB2F94E4E1004905B9 /* PersonalityEngine.swift in Sources */, diff --git a/nahbar/nahbar.xcodeproj/project.xcworkspace/xcuserdata/sven.xcuserdatad/UserInterfaceState.xcuserstate b/nahbar/nahbar.xcodeproj/project.xcworkspace/xcuserdata/sven.xcuserdatad/UserInterfaceState.xcuserstate index 5af515ec5823005dee0c1c6dbed3bcf121d457ef..9a1d95e1ffbb3ddc99a97cdd25b8d739c19c96e1 100644 GIT binary patch literal 94432 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

w>7u3<=IyHj!NkRh1&Je(g&G?&0V-& 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 Oqulxb`)A0o&;JVouoUwE literal 74581 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 K{;~gWMExH&AaA7r diff --git a/nahbar/nahbar/AddMomentView.swift b/nahbar/nahbar/AddMomentView.swift index 41e0d7c..4a0267a 100644 --- a/nahbar/nahbar/AddMomentView.swift +++ b/nahbar/nahbar/AddMomentView.swift @@ -278,7 +278,10 @@ struct AddMomentView: View { // Treffen: Callback für Rating-Flow + evtl. Kalendertermin if selectedType == .meeting { + // createdAt = Zeitpunkt des Treffens; wenn ein Zukunftstermin gewählt, + // bleibt der Moment zunächst unbewertet (kein sofortiger Rating-Flow) if addToCalendar { + moment.createdAt = eventDate let dateStr = eventDate.formatted(.dateTime.day().month(.abbreviated).hour().minute()) let calEntry = LogEntry( type: .calendarEvent, diff --git a/nahbar/nahbar/CallSuggestionView.swift b/nahbar/nahbar/CallSuggestionView.swift index 6801593..6f524b2 100644 --- a/nahbar/nahbar/CallSuggestionView.swift +++ b/nahbar/nahbar/CallSuggestionView.swift @@ -6,15 +6,12 @@ struct CallSuggestionView: View { @Bindable var person: Person let onConfirm: () -> Void - /// Zeigt PersonalityBadge wenn profil vorhanden und Person schon länger nicht besucht. + /// Zeigt PersonalityBadge wenn Profil vorhanden und letztes Treffen schon länger zurückliegt. private var showRecommendedBadge: Bool { guard let profile = PersonalityStore.shared.profile, profile.level(for: .agreeableness) == .high else { return false } - let lastVisit = person.visits? - .compactMap { $0.visitDate } - .max() - guard let lastVisit else { return true } - let days = Calendar.current.dateComponents([.day], from: lastVisit, to: Date()).day ?? 0 + guard let lastMeeting = person.lastMeetingDate else { return true } + let days = Calendar.current.dateComponents([.day], from: lastMeeting, to: Date()).day ?? 0 return days > 14 } @@ -63,12 +60,12 @@ struct CallSuggestionView: View { .clipShape(RoundedRectangle(cornerRadius: theme.radiusCard)) } - // Offener nächster Schritt - if let step = person.nextStep, !person.nextStepCompleted { + // Offenes Vorhaben (ersetzt nextStep) + if let intention = person.openIntentions.first { HStack(spacing: 8) { Image(systemName: "arrow.right.circle") .font(.system(size: 13)) - Text(step) + Text(intention.text) .font(.system(size: 14)) } .foregroundStyle(theme.accent.opacity(0.85)) diff --git a/nahbar/nahbar/ContentView.swift b/nahbar/nahbar/ContentView.swift index da5a61f..f69c4f4 100644 --- a/nahbar/nahbar/ContentView.swift +++ b/nahbar/nahbar/ContentView.swift @@ -9,6 +9,8 @@ struct ContentView: View { @AppStorage("callWindowOnboardingDone") private var onboardingDone = false @AppStorage("callSuggestionDate") private var suggestionDateStr = "" @AppStorage("photoRepairPassDone") private var photoRepairPassDone = false + @AppStorage("visitMigrationPassDone") private var visitMigrationPassDone = false + @AppStorage("nextStepMigrationPassDone") private var nextStepMigrationPassDone = false @EnvironmentObject private var callWindowManager: CallWindowManager @EnvironmentObject private var appLockManager: AppLockManager @@ -85,6 +87,8 @@ struct ContentView: View { syncPeopleCache() importPendingMoments() runPhotoRepairPass() + runVisitMigrationPass() + runNextStepMigrationPass() if !nahbarOnboardingDone { showingNahbarOnboarding = true } else if !onboardingDone { @@ -210,6 +214,114 @@ struct ContentView: View { AppEventLog.shared.record("Foto-Migration: \(personsNeedingRepair.count) Person(en) migriert", level: .success, category: "Migration") } + // MARK: - Visit Migration Pass (V4 → V5 Datenmigration) + + /// Überführt Visit-Objekte in Meeting-Momente. + /// Ratings und HealthSnapshot werden auf den neuen Moment umgehängt, BEVOR das Visit + /// gelöscht wird – sonst würde die Cascade-Delete-Rule die Ratings mitlöschen. + /// Läuft einmalig nach der Schema-V5-Migration. Danach: visitMigrationPassDone = true. + private func runVisitMigrationPass() { + guard !visitMigrationPassDone else { return } + + let descriptor = FetchDescriptor() + guard let visits = try? modelContext.fetch(descriptor) else { + visitMigrationPassDone = true + logger.info("Visit Migration Pass: Fetch fehlgeschlagen") + return + } + + guard !visits.isEmpty else { + visitMigrationPassDone = true + logger.info("Visit Migration Pass: nichts zu migrieren") + return + } + + logger.info("Visit Migration Pass: \(visits.count) Visit(s) werden migriert") + for visit in visits { + let moment = Moment( + text: visit.note ?? "", + type: .meeting, + source: nil, + person: visit.person + ) + moment.createdAt = visit.visitDate + moment.updatedAt = visit.visitDate + moment.statusRaw = visit.statusRaw + moment.aftermathNotificationScheduled = visit.aftermathNotificationScheduled + moment.aftermathCompletedAt = visit.aftermathCompletedAt + modelContext.insert(moment) + visit.person?.moments?.append(moment) + + // Ratings umhängen BEVOR Visit gelöscht wird (Cascade-Delete-Schutz) + for rating in (visit.ratings ?? []) { + rating.visit = nil + rating.moment = moment + } + + // HealthSnapshot umhängen + if let snapshot = visit.healthSnapshot { + snapshot.visit = nil + snapshot.moment = moment + } + + modelContext.delete(visit) + } + + save() + visitMigrationPassDone = true + logger.info("Visit Migration Pass abgeschlossen") + AppEventLog.shared.record( + "Treffen-Migration: \(visits.count) Visit(s) zu Momenten konvertiert", + level: .success, + category: "Migration" + ) + } + + // MARK: - Next Step Migration Pass (V4 → V5 Datenmigration) + + /// Überführt Person.nextStep-Felder in Vorhaben-Momente. + /// Läuft einmalig nach der Schema-V5-Migration. Danach: nextStepMigrationPassDone = true. + private func runNextStepMigrationPass() { + guard !nextStepMigrationPassDone else { return } + + let personsWithNextStep = persons.filter { + let step = $0.nextStep + return step != nil && !(step!.isEmpty) + } + + guard !personsWithNextStep.isEmpty else { + nextStepMigrationPassDone = true + logger.info("Next Step Migration Pass: nichts zu migrieren") + return + } + + logger.info("Next Step Migration Pass: \(personsWithNextStep.count) Person(en) werden migriert") + for person in personsWithNextStep { + guard let text = person.nextStep, !text.isEmpty else { continue } + + let moment = Moment(text: text, type: .intention, source: nil, person: person) + moment.reminderDate = person.nextStepReminderDate + moment.isCompleted = person.nextStepCompleted + modelContext.insert(moment) + person.moments?.append(moment) + + // Legacy-Felder leeren + person.nextStep = nil + person.nextStepCompleted = false + person.nextStepReminderDate = nil + person.touch() + } + + save() + nextStepMigrationPassDone = true + logger.info("Next Step Migration Pass abgeschlossen") + AppEventLog.shared.record( + "Vorhaben-Migration: \(personsWithNextStep.count) Nächste-Schritte zu Momenten konvertiert", + level: .success, + category: "Migration" + ) + } + // MARK: - Helpers private func save() { diff --git a/nahbar/nahbar/Localizable.xcstrings b/nahbar/nahbar/Localizable.xcstrings index c752374..7af5b8b 100644 --- a/nahbar/nahbar/Localizable.xcstrings +++ b/nahbar/nahbar/Localizable.xcstrings @@ -286,6 +286,7 @@ }, "Abgeschlossen" : { "comment" : "VisitHistorySection – visit status label", + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -539,6 +540,7 @@ }, "Anstehende Termine" : { "comment" : "TodayView – section title for upcoming reminders", + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -548,6 +550,17 @@ } } }, + "Anstehende Unternehmungen" : { + "comment" : "TodayView – section title for intention moments with reminder dates", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Upcoming Activities" + } + } + } + }, "App wirklich zurücksetzen?" : { }, @@ -664,6 +677,7 @@ }, "Besuch" : { "comment" : "PersonDetailView – button label to rate a new visit", + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -675,6 +689,7 @@ }, "Besuch bewerten" : { "comment" : "VisitRatingFlowView – navigation title", + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -696,8 +711,20 @@ } } }, + "Bewerten" : { + "comment" : "PersonDetailView – button label to open rating flow for a meeting moment", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rate" + } + } + } + }, "Bewertet" : { "comment" : "VisitHistorySection – visit status label for immediateCompleted", + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1587,11 +1614,23 @@ } } }, + "Erfasse Treffen als Moment und bewerte sie mit einem kurzen Fragebogen." : { + "comment" : "OnboardingContainerView – feature tour card description for Treffen", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Log meetings as a moment and rate them with a short questionnaire." + } + } + } + }, "Ergebnis bestätigen und fortfahren" : { }, "Erinnern" : { "comment" : "PersonDetailView – set reminder confirmation button", + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1601,8 +1640,20 @@ } } }, + "Erinnerung setzen" : { + "comment" : "AddMomentView – toggle label to enable reminder for intention moments", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Set reminder" + } + } + } + }, "Erinnerung setzen?" : { "comment" : "PersonDetailView – reminder prompt title", + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1966,6 +2017,17 @@ } } }, + "Geplante Treffen" : { + "comment" : "TodayView – section title for planned future meeting moments", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Planned Meetings" + } + } + } + }, "Gesprächszeit" : { "comment" : "SettingsView section header / CallWindowSetupView nav title", "localizations" : { @@ -2053,9 +2115,6 @@ } } } - }, - "Halte fest, wen du getroffen hast – und wann." : { - }, "Hat sich deine Sicht auf die Person verändert?" : { "comment" : "RatingQuestion – aftermath question text", @@ -2486,8 +2545,20 @@ "Kontakte überspringen?" : { }, - "Kontakte, Besuche und Momente bleiben lokal auf deinem Gerät – keine Cloud-Synchronisation." : { + "Kontakte und Momente bleiben lokal auf deinem Gerät – keine Cloud-Synchronisation." : { "comment" : "OnboardingPrivacyView – local storage privacy row text", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Contacts and moments stay local on your device – no cloud synchronisation." + } + } + } + }, + "Kontakte, Besuche und Momente bleiben lokal auf deinem Gerät – keine Cloud-Synchronisation." : { + "comment" : "OnboardingPrivacyView – local storage privacy row text (stale: replaced by V5 string)", + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -2680,6 +2751,7 @@ }, "Möchtest du die Notiz anpassen?" : { "comment" : "VisitEditFlowView – note step title", + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -2703,6 +2775,7 @@ }, "Möchtest du noch etwas festhalten?" : { "comment" : "VisitRatingFlowView – note step title", + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -2906,6 +2979,7 @@ }, "Nächster Schritt" : { "comment" : "PersonDetailView – next step section header", + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -2940,6 +3014,7 @@ }, "Nachwirkung ausstehend" : { "comment" : "VisitHistorySection – visit status label for awaitingAftermath", + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -3178,6 +3253,7 @@ }, "Noch keine Treffen bewertet" : { "comment" : "VisitHistorySection – empty state title", + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -3208,6 +3284,9 @@ } } } + }, + "Notiz anpassen" : { + }, "Notizen" : { "comment" : "AddPersonView / PersonDetailView – notes field label (plural)", @@ -3257,6 +3336,7 @@ }, "Offene Schritte" : { "comment" : "TodayView – section title for open next steps", + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -3266,8 +3346,20 @@ } } }, + "Offene Unternehmungen" : { + "comment" : "TodayView – section title for open intention moments without reminder", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Open Activities" + } + } + } + }, "Ok" : { "comment" : "PersonDetailView – next step confirmation button", + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -3388,8 +3480,28 @@ } } }, - "Plane gemeinsame Aktivitäten und bleib mit wichtigen Menschen in Kontakt." : { - + "Plane Unternehmungen mit Erinnerung – nahbar erinnert dich zur richtigen Zeit." : { + "comment" : "OnboardingContainerView – feature tour card description for Unternehmung (intention type)", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Plan activities with a reminder – nahbar notifies you at the right time." + } + } + } + }, + "Plane Vorhaben mit Erinnerung – nahbar erinnert dich zur richtigen Zeit." : { + "comment" : "OnboardingContainerView – stale, replaced by Momente wording", + "extractionState" : "stale", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Plan intentions with a reminder – nahbar notifies you at the right time." + } + } + } }, "PRO" : { "comment" : "Badge label for Pro tier", @@ -3539,6 +3651,7 @@ }, "Schritt definieren" : { "comment" : "PersonDetailView – define next step button", + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -3803,6 +3916,7 @@ }, "Tippe auf + um loszulegen" : { "comment" : "VisitHistorySection – empty state subtitle", + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -3844,6 +3958,9 @@ } } } + }, + "Treffen bewerten" : { + }, "Treffen mit %@" : { "comment" : "AddMomentView – calendar event / LogEntry title with person name", @@ -3982,6 +4099,17 @@ }, "Uns fehlt noch was – wir würden gerne mehr von dir erfahren." : { + }, + "Unternehmung" : { + "comment" : "MomentType.intention displayName – shown in type picker and feature tour", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Activity" + } + } + } }, "Unwohl" : { "comment" : "RatingQuestion – negative pole for comfort during meeting", @@ -4130,7 +4258,8 @@ } }, "Vorhaben" : { - "comment" : "MomentType.intention raw value", + "comment" : "MomentType.intention raw value (stale: displayName now returns 'Momente')", + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -4232,6 +4361,7 @@ }, "Was als Nächstes?" : { "comment" : "PersonDetailView – next step input placeholder", + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -4243,6 +4373,7 @@ }, "Was war der Kern des Gesprächs?\nWas möchtest du nicht vergessen?" : { "comment" : "AddMomentView – text editor placeholder", + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { diff --git a/nahbar/nahbar/LogbuchView.swift b/nahbar/nahbar/LogbuchView.swift index 0c9be24..3751235 100644 --- a/nahbar/nahbar/LogbuchView.swift +++ b/nahbar/nahbar/LogbuchView.swift @@ -165,17 +165,34 @@ struct LogbuchView: View { private func logbuchRow(item: LogbuchItem) -> some View { HStack(alignment: .top, spacing: 12) { - Image(systemName: item.icon) - .font(.system(size: 14, weight: .light)) - .foregroundStyle(item.isLogEntry ? theme.accent : theme.contentTertiary) - .frame(width: 20) - .padding(.top, 2) + // Typ-Icon (bei Vorhaben: Checkbox-Status) + Group { + if case .moment(let m) = item, m.isIntention { + Image(systemName: m.isCompleted ? "checkmark.circle.fill" : "circle") + .foregroundStyle(m.isCompleted ? Color.green : theme.contentTertiary) + } else { + Image(systemName: item.icon) + .foregroundStyle(item.isLogEntry ? theme.accent : theme.contentTertiary) + } + } + .font(.system(size: 14, weight: .light)) + .frame(width: 20) + .padding(.top, 2) VStack(alignment: .leading, spacing: 3) { - Text(item.title) - .font(.system(size: 15, design: theme.displayDesign)) - .foregroundStyle(theme.contentPrimary) - .fixedSize(horizontal: false, vertical: true) + // Titel (Vorhaben: Durchgestrichen wenn erledigt) + if case .moment(let m) = item, m.isIntention, m.isCompleted { + Text(item.title) + .font(.system(size: 15, design: theme.displayDesign)) + .foregroundStyle(theme.contentTertiary) + .strikethrough(true, color: theme.contentTertiary) + .fixedSize(horizontal: false, vertical: true) + } else { + Text(item.title) + .font(.system(size: 15, design: theme.displayDesign)) + .foregroundStyle(theme.contentPrimary) + .fixedSize(horizontal: false, vertical: true) + } HStack(spacing: 6) { if case .moment(let m) = item, m.isImportant { @@ -196,11 +213,33 @@ struct LogbuchView: View { } Spacer() + + // Score-Badge für bewertete Treffen + if case .moment(let m) = item, m.isMeeting, let avg = m.immediateAverage { + ZStack { + Circle() + .fill(scoreColor(avg).opacity(0.15)) + .frame(width: 30, height: 30) + Text(String(format: "%.1f", avg)) + .font(.system(size: 9, weight: .bold)) + .foregroundStyle(scoreColor(avg)) + } + .padding(.top, 2) + } } .padding(.horizontal, 16) .padding(.vertical, 12) } + private func scoreColor(_ value: Double) -> Color { + switch value { + case ..<(-0.5): return .red + case (-0.5)..<(0.5): return Color(.systemGray3) + case (0.5)...: return .green + default: return .gray + } + } + // MARK: - Empty State private var emptyState: some View { diff --git a/nahbar/nahbar/Models.swift b/nahbar/nahbar/Models.swift index 3fa093e..10c8f8b 100644 --- a/nahbar/nahbar/Models.swift +++ b/nahbar/nahbar/Models.swift @@ -46,7 +46,12 @@ enum MomentType: String, CaseIterable, Codable { case intention = "Vorhaben" /// Anzeigename im UI — entkoppelt Persistenzschlüssel von der Darstellung. - var displayName: String { rawValue } + var displayName: String { + switch self { + case .intention: return "Unternehmung" + default: return rawValue + } + } var icon: String { switch self { @@ -233,6 +238,36 @@ class Person { (visits ?? []).count } + // MARK: Meeting-Momente (V5) + + var meetingMoments: [Moment] { + (moments ?? []).filter { $0.type == .meeting } + } + + var sortedMeetings: [Moment] { + meetingMoments.sorted { $0.createdAt > $1.createdAt } + } + + var lastMeeting: Moment? { + sortedMeetings.first + } + + var lastMeetingDate: Date? { + lastMeeting?.createdAt + } + + var meetingCount: Int { + meetingMoments.count + } + + // MARK: Offene Vorhaben (V5) + + /// Alle Vorhaben-Momente, die noch nicht abgehakt wurden. + var openIntentions: [Moment] { + (moments ?? []).filter { $0.isOpen } + .sorted { $0.createdAt < $1.createdAt } + } + /// Muss nach jeder inhaltlichen Änderung aufgerufen werden. func touch() { updatedAt = Date() @@ -302,6 +337,17 @@ class Moment { var isImportant: Bool = false // Vom Nutzer als wichtig markiert var person: Person? + // V5: Meeting-Bewertungsfelder (ehemals auf Visit) + @Relationship(deleteRule: .cascade) var ratings: [Rating]? = [] + @Relationship(deleteRule: .cascade) var healthSnapshot: HealthSnapshot? = nil + var statusRaw: String? = nil // nil für Nicht-Treffen; VisitStatus-rawValue für Treffen + var aftermathNotificationScheduled: Bool = false + var aftermathCompletedAt: Date? = nil + + // V5: Vorhaben-Felder (ehemals Person.nextStep / nextStepReminderDate) + var reminderDate: Date? = nil // Erinnerungstermin für Vorhaben + var isCompleted: Bool = false // Ob das Vorhaben abgehakt wurde + init(text: String, type: MomentType = .conversation, source: MomentSource? = nil, person: Person? = nil) { self.id = UUID() self.text = text @@ -311,6 +357,13 @@ class Moment { self.updatedAt = Date() self.isImportant = false self.person = person + self.ratings = [] + self.healthSnapshot = nil + self.statusRaw = nil + self.aftermathNotificationScheduled = false + self.aftermathCompletedAt = nil + self.reminderDate = nil + self.isCompleted = false } var type: MomentType { @@ -322,6 +375,51 @@ class Moment { get { sourceRaw.flatMap { MomentSource(rawValue: $0) } } set { sourceRaw = newValue?.rawValue } } + + // MARK: Meeting-Hilfseigenschaften (V5) + + var isMeeting: Bool { type == .meeting } + + var meetingStatus: VisitStatus? { + get { statusRaw.flatMap { VisitStatus(rawValue: $0) } } + set { statusRaw = newValue?.rawValue } + } + + var isMeetingComplete: Bool { + meetingStatus == .completed + } + + var sortedRatings: [Rating] { + (ratings ?? []).sorted { $0.questionIndex < $1.questionIndex } + } + + func averageForCategory(_ category: RatingCategory, aftermath: Bool) -> Double? { + let filtered = (ratings ?? []).filter { + $0.category == category && $0.isAftermath == aftermath + } + let valued = filtered.compactMap { $0.value } + guard !valued.isEmpty else { return nil } + return Double(valued.reduce(0, +)) / Double(valued.count) + } + + var immediateAverage: Double? { + let values = (ratings ?? []).filter { !$0.isAftermath }.compactMap { $0.value } + guard !values.isEmpty else { return nil } + return Double(values.reduce(0, +)) / Double(values.count) + } + + var aftermathAverage: Double? { + let values = (ratings ?? []).filter { $0.isAftermath }.compactMap { $0.value } + guard !values.isEmpty else { return nil } + return Double(values.reduce(0, +)) / Double(values.count) + } + + // MARK: Vorhaben-Hilfseigenschaften (V5) + + var isIntention: Bool { type == .intention } + + /// Ein Vorhaben das noch nicht abgehakt wurde. + var isOpen: Bool { isIntention && !isCompleted } } // MARK: - Visit Rating Enums @@ -497,15 +595,18 @@ class Rating { var questionIndex: Int = 0 var value: Int? = nil // nil = übersprungen; -2...+2 sonst var isAftermath: Bool = false - var visit: Visit? = nil + var visit: Visit? = nil // V4 legacy – nach Migration immer nil + var moment: Moment? = nil // V5 – primäre Referenz - init(category: RatingCategory, questionIndex: Int, value: Int?, isAftermath: Bool, visit: Visit? = nil) { + init(category: RatingCategory, questionIndex: Int, value: Int?, isAftermath: Bool, + visit: Visit? = nil, moment: Moment? = nil) { self.id = UUID() self.categoryRaw = category.rawValue self.questionIndex = questionIndex self.value = value self.isAftermath = isAftermath self.visit = visit + self.moment = moment } var category: RatingCategory { @@ -523,11 +624,13 @@ class HealthSnapshot { var hrvMs: Double? = nil var restingHR: Int? = nil var steps: Int? = nil - var visit: Visit? = nil + var visit: Visit? = nil // V4 legacy – nach Migration immer nil + var moment: Moment? = nil // V5 – primäre Referenz - init(visit: Visit? = nil) { + init(visit: Visit? = nil, moment: Moment? = nil) { self.id = UUID() self.visit = visit + self.moment = moment } var hasData: Bool { diff --git a/nahbar/nahbar/NahbarMigration.swift b/nahbar/nahbar/NahbarMigration.swift index 0b04af7..722c209 100644 --- a/nahbar/nahbar/NahbarMigration.swift +++ b/nahbar/nahbar/NahbarMigration.swift @@ -186,18 +186,123 @@ enum NahbarSchemaV3: VersionedSchema { } } -// MARK: - Schema V4 (aktuelles Schema) -// Referenziert die Live-Typen aus Models.swift. -// Beim Hinzufügen von V5 muss V4 als eingefrorener Snapshot gesichert werden. +// MARK: - Schema V4 (eingefrorener Snapshot) +// WICHTIG: Niemals nachträglich ändern – dieser Snapshot muss dem gespeicherten +// Schema-Hash von V4-Datenbanken auf Nutzer-Geräten entsprechen. // -// V4 fügt hinzu: +// V4 fügte hinzu: // • Visit, Rating, HealthSnapshot: neue Modelle für Besuchs-Bewertungen // • Person: visits-Relationship enum NahbarSchemaV4: VersionedSchema { static var versionIdentifier = Schema.Version(4, 0, 0) static var models: [any PersistentModel.Type] { - [nahbar.Person.self, nahbar.Moment.self, nahbar.LogEntry.self, nahbar.PersonPhoto.self, + [PersonPhoto.self, Person.self, Moment.self, LogEntry.self, + Visit.self, Rating.self, HealthSnapshot.self] + } + + @Model final class PersonPhoto { + var id: UUID = UUID() + @Attribute(.externalStorage) var imageData: Data = Data() + var createdAt: Date = Date() + init() {} + } + + @Model final class Person { + var id: UUID = UUID() + var name: String = "" + var tagRaw: String = "Andere" + var birthday: Date? = nil + var occupation: String? = nil + var location: String? = nil + var interests: String? = nil + var generalNotes: String? = nil + var nudgeFrequencyRaw: String = "Monatlich" + var nextStep: String? = nil + var nextStepCompleted: Bool = false + var nextStepReminderDate: Date? = nil + var lastSuggestedForCall: Date? = nil + var createdAt: Date = Date() + var updatedAt: Date = Date() + var isArchived: Bool = false + @Relationship(deleteRule: .cascade) var photo: PersonPhoto? = nil + var photoData: Data? = nil + @Relationship(deleteRule: .cascade) var moments: [Moment]? = [] + @Relationship(deleteRule: .cascade) var logEntries: [LogEntry]? = [] + @Relationship(deleteRule: .cascade) var visits: [Visit]? = [] + init() {} + } + + @Model final class Moment { + var id: UUID = UUID() + var text: String = "" + var typeRaw: String = "Gespräch" + var sourceRaw: String? = nil + var createdAt: Date = Date() + var updatedAt: Date = Date() + var isImportant: Bool = false + var person: Person? = nil + init() {} + } + + @Model final class LogEntry { + var id: UUID = UUID() + var typeRaw: String = "Schritt abgeschlossen" + var title: String = "" + var loggedAt: Date = Date() + var updatedAt: Date = Date() + var person: Person? = nil + init() {} + } + + @Model final class Visit { + var id: UUID = UUID() + var visitDate: Date = Date() + var statusRaw: String = "sofort_abgeschlossen" + var note: String? = nil + var aftermathNotificationScheduled: Bool = false + var aftermathCompletedAt: Date? = nil + var person: Person? = nil + @Relationship(deleteRule: .cascade) var ratings: [Rating]? = [] + @Relationship(deleteRule: .cascade) var healthSnapshot: HealthSnapshot? = nil + init() {} + } + + @Model final class Rating { + var id: UUID = UUID() + var categoryRaw: String = "Selbst" + var questionIndex: Int = 0 + var value: Int? = nil + var isAftermath: Bool = false + var visit: Visit? = nil + init() {} + } + + @Model final class HealthSnapshot { + var id: UUID = UUID() + var sleepHours: Double? = nil + var hrvMs: Double? = nil + var restingHR: Int? = nil + var steps: Int? = nil + var visit: Visit? = nil + init() {} + } +} + +// MARK: - Schema V5 (aktuelles Schema) +// Referenziert die Live-Typen aus Models.swift. +// Beim Hinzufügen von V6 muss V5 als eingefrorener Snapshot gesichert werden. +// +// V5 fügt hinzu: +// • Moment: ratings, healthSnapshot, statusRaw, aftermathNotificationScheduled, +// aftermathCompletedAt, reminderDate, isCompleted +// • Rating: moment-Relationship (neben legacy visit) +// • HealthSnapshot: moment-Relationship (neben legacy visit) + +enum NahbarSchemaV5: VersionedSchema { + static var versionIdentifier = Schema.Version(5, 0, 0) + static var models: [any PersistentModel.Type] { + [nahbar.PersonPhoto.self, nahbar.Person.self, nahbar.Moment.self, nahbar.LogEntry.self, nahbar.Visit.self, nahbar.Rating.self, nahbar.HealthSnapshot.self] } } @@ -206,7 +311,8 @@ enum NahbarSchemaV4: VersionedSchema { enum NahbarMigrationPlan: SchemaMigrationPlan { static var schemas: [any VersionedSchema.Type] { - [NahbarSchemaV1.self, NahbarSchemaV2.self, NahbarSchemaV3.self, NahbarSchemaV4.self] + [NahbarSchemaV1.self, NahbarSchemaV2.self, NahbarSchemaV3.self, + NahbarSchemaV4.self, NahbarSchemaV5.self] } static var stages: [MigrationStage] { @@ -221,7 +327,12 @@ enum NahbarMigrationPlan: SchemaMigrationPlan { // V3 → V4: Visit/Rating/HealthSnapshot neu, Person bekommt visits-Relationship. // Alle neuen Felder haben Default-Werte → lightweight-Migration reicht aus. - .lightweight(fromVersion: NahbarSchemaV3.self, toVersion: NahbarSchemaV4.self) + .lightweight(fromVersion: NahbarSchemaV3.self, toVersion: NahbarSchemaV4.self), + + // V4 → V5: Moment bekommt rating/intention-Felder, Rating/HealthSnapshot + // bekommen moment-Relationship. Visit/HealthSnapshot bleiben im Schema + // (CloudKit-Sicherheit). Alle neuen Felder haben Default-Werte → lightweight. + .lightweight(fromVersion: NahbarSchemaV4.self, toVersion: NahbarSchemaV5.self) ] } } diff --git a/nahbar/nahbar/OnboardingContainerView.swift b/nahbar/nahbar/OnboardingContainerView.swift index c3443b8..d3b4a64 100644 --- a/nahbar/nahbar/OnboardingContainerView.swift +++ b/nahbar/nahbar/OnboardingContainerView.swift @@ -605,7 +605,7 @@ private struct OnboardingPrivacyView: View { VStack(alignment: .leading, spacing: 20) { privacyRow( icon: "iphone", - text: "Kontakte, Besuche und Momente bleiben lokal auf deinem Gerät – keine Cloud-Synchronisation." + text: "Kontakte und Momente bleiben lokal auf deinem Gerät – keine Cloud-Synchronisation." ) privacyRow( icon: "person.slash", @@ -664,14 +664,14 @@ struct FeatureTourStep { static let all: [FeatureTourStep] = [ FeatureTourStep( icon: "checklist", - title: "Vorhaben", - description: "Plane gemeinsame Aktivitäten und bleib mit wichtigen Menschen in Kontakt.", + title: "Unternehmung", + description: "Plane Unternehmungen mit Erinnerung – nahbar erinnert dich zur richtigen Zeit.", showPrivacySummary: false ), FeatureTourStep( icon: "figure.walk.arrival", title: "Treffen", - description: "Halte fest, wen du getroffen hast – und wann.", + description: "Erfasse Treffen als Moment und bewerte sie mit einem kurzen Fragebogen.", showPrivacySummary: false ), FeatureTourStep( diff --git a/nahbar/nahbar/PersonDetailView.swift b/nahbar/nahbar/PersonDetailView.swift index 1429d47..4d59719 100644 --- a/nahbar/nahbar/PersonDetailView.swift +++ b/nahbar/nahbar/PersonDetailView.swift @@ -43,7 +43,8 @@ struct PersonDetailView: View { } .sheet(isPresented: $showingAddMoment) { AddMomentView(person: person) { meetingMoment in - // Nach Sheet-Dismiss kurz warten, dann Rating-Flow öffnen + // Rating-Flow nur für vergangene Treffen – zukünftige Termine überspringen + guard meetingMoment.createdAt <= Date() else { return } DispatchQueue.main.asyncAfter(deadline: .now() + 0.45) { momentForRating = meetingMoment } diff --git a/nahbar/nahbar/PersonalityEngine.swift b/nahbar/nahbar/PersonalityEngine.swift index 6ced43f..2d9811b 100644 --- a/nahbar/nahbar/PersonalityEngine.swift +++ b/nahbar/nahbar/PersonalityEngine.swift @@ -106,7 +106,7 @@ enum PersonalityEngine { static func sortedSuggestions( contacts: [NahbarContact], profile: PersonalityProfile?, - lastVisitDates: [UUID: Date] + lastMeetingDates: [UUID: Date] ) -> [ContactSuggestion] { guard let profile else { return contacts.map { ContactSuggestion(contact: $0, isRecommended: false, reason: nil) } @@ -127,16 +127,16 @@ enum PersonalityEngine { var sorted = contacts if a == .high { sorted = contacts.sorted { lhs, rhs in - let lDate = lastVisitDates[lhs.id] ?? .distantPast - let rDate = lastVisitDates[rhs.id] ?? .distantPast - return lDate < rDate // ältester Besuch zuerst + let lDate = lastMeetingDates[lhs.id] ?? .distantPast + let rDate = lastMeetingDates[rhs.id] ?? .distantPast + return lDate < rDate // ältestes Treffen zuerst } } return sorted.prefix(maxCount).map { contact in let longAgo: Bool - if let lastVisit = lastVisitDates[contact.id] { - let daysSince = Calendar.current.dateComponents([.day], from: lastVisit, to: Date()).day ?? 0 + if let lastMeeting = lastMeetingDates[contact.id] { + let daysSince = Calendar.current.dateComponents([.day], from: lastMeeting, to: Date()).day ?? 0 longAgo = daysSince > 14 } else { longAgo = true diff --git a/nahbar/nahbar/SettingsView.swift b/nahbar/nahbar/SettingsView.swift index b8bc6a0..9975d54 100644 --- a/nahbar/nahbar/SettingsView.swift +++ b/nahbar/nahbar/SettingsView.swift @@ -605,11 +605,13 @@ struct SettingsView: View { UserProfileStore.shared.reset() ContactStore.shared.reset() - // 3. Onboarding-Flags zurücksetzen + // 3. Onboarding- und Migrations-Flags zurücksetzen nahbarOnboardingDone = false callWindowOnboardingDone = false photoRepairPassDone = false callSuggestionDate = "" + UserDefaults.standard.removeObject(forKey: "visitMigrationPassDone") + UserDefaults.standard.removeObject(forKey: "nextStepMigrationPassDone") // 4. App neu starten damit alle States frisch initialisiert werden DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { exit(0) } diff --git a/nahbar/nahbar/TodayView.swift b/nahbar/nahbar/TodayView.swift index 321a151..6c3d3bd 100644 --- a/nahbar/nahbar/TodayView.swift +++ b/nahbar/nahbar/TodayView.swift @@ -4,11 +4,12 @@ import SwiftData struct TodayView: View { @Environment(\.nahbarTheme) var theme @Query private var people: [Person] - @Query(filter: #Predicate { $0.statusRaw == "warte_nachwirkung" }, - sort: \Visit.visitDate, order: .reverse) - private var pendingAftermaths: [Visit] - @State private var showingAftermathRating = false - @State private var selectedVisitForAftermath: Visit? = nil + // V5: Nachwirkungen sind jetzt Treffen-Momente mit Status "warte_nachwirkung" + @Query(filter: #Predicate { + $0.statusRaw == "warte_nachwirkung" && $0.typeRaw == "Treffen" + }, sort: \Moment.createdAt, order: .reverse) + private var pendingAftermaths: [Moment] + @State private var selectedMomentForAftermath: Moment? = nil @AppStorage("upcomingDaysAhead") private var daysAhead: Int = 7 private var needsAttention: [Person] { @@ -21,28 +22,42 @@ struct TodayView: View { people.filter { $0.hasBirthdayWithin(days: daysAhead) } } - // People with a scheduled reminder within the look-ahead window - private var upcomingReminders: [Person] { - let horizon = Calendar.current.date(byAdding: .day, value: daysAhead, to: Date()) ?? Date() - return people - .filter { p in - guard let reminder = p.nextStepReminderDate, !p.nextStepCompleted else { return false } - return reminder >= Date() && reminder <= horizon - } - .sorted { ($0.nextStepReminderDate ?? Date()) < ($1.nextStepReminderDate ?? Date()) } + // V5: Geplante Treffen (Datum in der Zukunft, noch nicht bewertet) + @Query(filter: #Predicate { + $0.typeRaw == "Treffen" && $0.statusRaw == nil + }, sort: \Moment.createdAt) + private var allUnratedMeetings: [Moment] + + private var plannedMeetings: [Moment] { + let now = Date() + let horizon = Calendar.current.date(byAdding: .day, value: daysAhead, to: now) ?? now + return allUnratedMeetings.filter { $0.createdAt > now && $0.createdAt <= horizon } } - // Open next steps NOT already shown under upcoming reminders - private var openNextSteps: [Person] { - let scheduledIDs = Set(upcomingReminders.map { $0.id }) - return people.filter { p in - p.nextStep != nil && !p.nextStepCompleted && !scheduledIDs.contains(p.id) + // V5: Vorhaben-Momente mit Erinnerung innerhalb des Zeitfensters + @Query(filter: #Predicate { + $0.typeRaw == "Vorhaben" && !$0.isCompleted && $0.reminderDate != nil + }, sort: \Moment.reminderDate) + private var allIntentionsWithReminder: [Moment] + + private var upcomingReminders: [Moment] { + let now = Date() + let horizon = Calendar.current.date(byAdding: .day, value: daysAhead, to: now) ?? now + return allIntentionsWithReminder.filter { m in + guard let r = m.reminderDate else { return false } + return r >= now && r <= horizon } } + // V5: Vorhaben ohne Erinnerung (offene Schritte) + @Query(filter: #Predicate { + $0.typeRaw == "Vorhaben" && !$0.isCompleted && $0.reminderDate == nil + }, sort: \Moment.createdAt) + private var openIntentions: [Moment] + private var isEmpty: Bool { - needsAttention.isEmpty && birthdayPeople.isEmpty && openNextSteps.isEmpty - && upcomingReminders.isEmpty && pendingAftermaths.isEmpty + needsAttention.isEmpty && birthdayPeople.isEmpty && openIntentions.isEmpty + && upcomingReminders.isEmpty && pendingAftermaths.isEmpty && plannedMeetings.isEmpty } private var birthdaySectionTitle: LocalizedStringKey { @@ -103,28 +118,48 @@ struct TodayView: View { } } - if !upcomingReminders.isEmpty { - TodaySection(title: "Anstehende Termine", icon: "calendar") { - ForEach(upcomingReminders) { person in - NavigationLink(destination: PersonDetailView(person: person)) { - TodayRow(person: person, hint: reminderHint(for: person)) + if !plannedMeetings.isEmpty { + TodaySection(title: "Geplante Treffen", icon: "calendar.badge.clock") { + ForEach(plannedMeetings) { moment in + if let person = moment.person { + NavigationLink(destination: PersonDetailView(person: person)) { + TodayRow(person: person, hint: plannedMeetingHint(for: moment)) + } + .buttonStyle(.plain) } - .buttonStyle(.plain) - if person.id != upcomingReminders.last?.id { + if moment.id != plannedMeetings.last?.id { RowDivider() } } } } - if !openNextSteps.isEmpty { - TodaySection(title: "Offene Schritte", icon: "arrow.right.circle") { - ForEach(openNextSteps) { person in - NavigationLink(destination: PersonDetailView(person: person)) { - TodayRow(person: person, hint: person.nextStep ?? "") + if !upcomingReminders.isEmpty { + TodaySection(title: "Anstehende Unternehmungen", icon: "calendar") { + ForEach(upcomingReminders) { moment in + if let person = moment.person { + NavigationLink(destination: PersonDetailView(person: person)) { + TodayRow(person: person, hint: intentionReminderHint(for: moment)) + } + .buttonStyle(.plain) } - .buttonStyle(.plain) - if person.id != openNextSteps.last?.id { + if moment.id != upcomingReminders.last?.id { + RowDivider() + } + } + } + } + + if !openIntentions.isEmpty { + TodaySection(title: "Offene Unternehmungen", icon: "arrow.right.circle") { + ForEach(openIntentions) { moment in + if let person = moment.person { + NavigationLink(destination: PersonDetailView(person: person)) { + TodayRow(person: person, hint: moment.text) + } + .buttonStyle(.plain) + } + if moment.id != openIntentions.last?.id { RowDivider() } } @@ -133,20 +168,19 @@ struct TodayView: View { if !pendingAftermaths.isEmpty { TodaySection(title: "Nachwirkung fällig", icon: "moon.stars.fill") { - ForEach(pendingAftermaths) { visit in + ForEach(pendingAftermaths) { moment in Button { - selectedVisitForAftermath = visit - showingAftermathRating = true + selectedMomentForAftermath = moment } label: { HStack(spacing: 12) { - if let p = visit.person { + if let p = moment.person { PersonAvatar(person: p, size: 36) } VStack(alignment: .leading, spacing: 2) { - Text(visit.person?.name ?? "Unbekannt") + Text(moment.person?.name ?? "Unbekannt") .font(.system(size: 15, weight: .medium)) .foregroundStyle(theme.contentPrimary) - Text("Treffen \(visit.visitDate.formatted(date: .abbreviated, time: .omitted))") + Text("Treffen \(moment.createdAt.formatted(date: .abbreviated, time: .omitted))") .font(.system(size: 13)) .foregroundStyle(theme.contentTertiary) } @@ -159,7 +193,7 @@ struct TodayView: View { .padding(.vertical, 12) } .buttonStyle(.plain) - if visit.id != pendingAftermaths.last?.id { + if moment.id != pendingAftermaths.last?.id { RowDivider() } } @@ -186,8 +220,8 @@ struct TodayView: View { } .background(theme.backgroundPrimary.ignoresSafeArea()) .navigationBarHidden(true) - .sheet(item: $selectedVisitForAftermath) { visit in - AftermathRatingFlowView(visit: visit) + .sheet(item: $selectedMomentForAftermath) { moment in + AftermathRatingFlowView(moment: moment) } } } @@ -226,10 +260,15 @@ struct TodayView: View { return "" } - private func reminderHint(for person: Person) -> String { - guard let reminder = person.nextStepReminderDate else { return person.nextStep ?? "" } + private func plannedMeetingHint(for moment: Moment) -> String { + let dateStr = moment.createdAt.formatted(.dateTime.weekday(.abbreviated).day().month(.abbreviated).hour().minute()) + return moment.text.isEmpty ? dateStr : "\(moment.text) · \(dateStr)" + } + + private func intentionReminderHint(for moment: Moment) -> String { + guard let reminder = moment.reminderDate else { return moment.text } let dateStr = reminder.formatted(.dateTime.day().month(.abbreviated).hour().minute()) - return "\(person.nextStep ?? "") · \(dateStr)" + return "\(moment.text) · \(dateStr)" } private func lastSeenHint(for person: Person) -> String { diff --git a/nahbar/nahbarTests/AppEventLogTests.swift b/nahbar/nahbarTests/AppEventLogTests.swift index 5ca1742..8e279c1 100644 --- a/nahbar/nahbarTests/AppEventLogTests.swift +++ b/nahbar/nahbarTests/AppEventLogTests.swift @@ -155,14 +155,14 @@ struct SchemaRegressionTests { #expect(NahbarSchemaV3.versionIdentifier.patch == 0) } - @Test("Migrationsplan enthält genau 4 Schemas") - func migrationPlanHasFourSchemas() { - #expect(NahbarMigrationPlan.schemas.count == 4) + @Test("Migrationsplan enthält genau 5 Schemas") + func migrationPlanHasFiveSchemas() { + #expect(NahbarMigrationPlan.schemas.count == 5) } - @Test("Migrationsplan enthält genau 3 Stages") - func migrationPlanHasThreeStages() { - #expect(NahbarMigrationPlan.stages.count == 3) + @Test("Migrationsplan enthält genau 4 Stages") + func migrationPlanHasFourStages() { + #expect(NahbarMigrationPlan.stages.count == 4) } @Test("ContainerFallback-Gleichheit funktioniert korrekt") diff --git a/nahbar/nahbarTests/ModelTests.swift b/nahbar/nahbarTests/ModelTests.swift index 8f67894..f412faa 100644 --- a/nahbar/nahbarTests/ModelTests.swift +++ b/nahbar/nahbarTests/ModelTests.swift @@ -91,14 +91,20 @@ struct MomentTypeTests { } } - @Test("displayName ist für alle Types gleich rawValue") - func displayNameEqualsRawValueForAllTypes() { - for type_ in MomentType.allCases { + @Test("displayName entspricht rawValue außer für .intention") + func displayNameEqualsRawValueExceptIntention() { + for type_ in MomentType.allCases where type_ != .intention { #expect(type_.displayName == type_.rawValue, "displayName sollte rawValue sein für \(type_)") } } + @Test(".intention hat displayName 'Unternehmung' (entkoppelt von rawValue 'Vorhaben')") + func intentionDisplayNameIsUnternehmung() { + #expect(MomentType.intention.displayName == "Unternehmung") + #expect(MomentType.intention.rawValue == "Vorhaben") // Persistenz-Key bleibt + } + @Test("alle Types haben nicht-leeres displayName") func allTypesHaveNonEmptyDisplayName() { for type_ in MomentType.allCases { @@ -287,6 +293,67 @@ struct MomentComputedPropertyTests { let m = Moment(text: "Test") #expect(!m.isImportant) } + + // V5 – Meeting-Felder + @Test("V5: statusRaw startet als nil") + func statusRawDefaultsNil() { + let m = Moment(text: "Test", type: .meeting) + #expect(m.statusRaw == nil) + } + + @Test("V5: aftermathNotificationScheduled startet als false") + func aftermathNotificationScheduledDefaultsFalse() { + let m = Moment(text: "Test") + #expect(!m.aftermathNotificationScheduled) + } + + @Test("V5: isCompleted startet als false") + func isCompletedDefaultsFalse() { + let m = Moment(text: "Test") + #expect(!m.isCompleted) + } + + @Test("V5: reminderDate startet als nil") + func reminderDateDefaultsNil() { + let m = Moment(text: "Test", type: .intention) + #expect(m.reminderDate == nil) + } + + @Test("V5: isMeeting true nur für .meeting") + func isMeetingOnlyForMeetingType() { + let meeting = Moment(text: "Test", type: .meeting) + let other = Moment(text: "Test", type: .conversation) + #expect(meeting.isMeeting) + #expect(!other.isMeeting) + } + + @Test("V5: isIntention true nur für .intention") + func isIntentionOnlyForIntentionType() { + let intention = Moment(text: "Test", type: .intention) + let other = Moment(text: "Test", type: .thought) + #expect(intention.isIntention) + #expect(!other.isIntention) + } + + @Test("V5: isOpen true wenn intention && nicht erledigt") + func isOpenWhenNotCompleted() { + let m = Moment(text: "Test", type: .intention) + m.isCompleted = false + #expect(m.isOpen) + m.isCompleted = true + #expect(!m.isOpen) + } + + @Test("V5: meetingStatus round-trip via statusRaw") + func meetingStatusRoundTrip() { + let m = Moment(text: "Test", type: .meeting) + m.meetingStatus = .awaitingAftermath + #expect(m.meetingStatus == .awaitingAftermath) + #expect(m.statusRaw == VisitStatus.awaitingAftermath.rawValue) + m.meetingStatus = .completed + #expect(m.meetingStatus == .completed) + #expect(m.isMeetingComplete) + } } // MARK: - LogEntry Tests diff --git a/nahbar/nahbarTests/NahbarPersonalityTests.swift b/nahbar/nahbarTests/NahbarPersonalityTests.swift index 7bed747..e383d81 100644 --- a/nahbar/nahbarTests/NahbarPersonalityTests.swift +++ b/nahbar/nahbarTests/NahbarPersonalityTests.swift @@ -409,7 +409,7 @@ struct PersonalityEngineBehaviorTests { let suggestions = PersonalityEngine.sortedSuggestions( contacts: [], profile: nil, - lastVisitDates: [:] + lastMeetingDates: [:] ) for s in suggestions { #expect(!s.isRecommended) diff --git a/nahbar/nahbarTests/VisitRatingTests.swift b/nahbar/nahbarTests/VisitRatingTests.swift index 7c8d09e..4a8cb3e 100644 --- a/nahbar/nahbarTests/VisitRatingTests.swift +++ b/nahbar/nahbarTests/VisitRatingTests.swift @@ -323,9 +323,9 @@ struct AftermathNotificationManagerTests { #expect(AftermathNotificationManager.actionID == "RATE_NOW") } - @Test("visitIDKey ist unveränderlich") - func visitIDKey() { - #expect(AftermathNotificationManager.visitIDKey == "visitID") + @Test("momentIDKey ist unveränderlich") + func momentIDKey() { + #expect(AftermathNotificationManager.momentIDKey == "momentID") } @Test("personNameKey ist unveränderlich") @@ -345,14 +345,27 @@ struct SchemaV4RegressionTests { #expect(NahbarSchemaV4.versionIdentifier.minor == 0) #expect(NahbarSchemaV4.versionIdentifier.patch == 0) } +} - @Test("Migrationsplan enthält genau 4 Schemas") - func migrationPlanHasFourSchemas() { - #expect(NahbarMigrationPlan.schemas.count == 4) +// MARK: - Schema-Regressionswächter (V5) + +@Suite("Schema – Regressionswächter V5") +struct SchemaV5RegressionTests { + + @Test("NahbarSchemaV5 hat Version 5.0.0") + func schemaV5HasCorrectVersion() { + #expect(NahbarSchemaV5.versionIdentifier.major == 5) + #expect(NahbarSchemaV5.versionIdentifier.minor == 0) + #expect(NahbarSchemaV5.versionIdentifier.patch == 0) } - @Test("Migrationsplan enthält genau 3 Stages") - func migrationPlanHasThreeStages() { - #expect(NahbarMigrationPlan.stages.count == 3) + @Test("Migrationsplan enthält genau 5 Schemas") + func migrationPlanHasFiveSchemas() { + #expect(NahbarMigrationPlan.schemas.count == 5) + } + + @Test("Migrationsplan enthält genau 4 Stages") + func migrationPlanHasFourStages() { + #expect(NahbarMigrationPlan.stages.count == 4) } }