From e365400537db2cec339ea668b22943106567f997 Mon Sep 17 00:00:00 2001 From: Sven Date: Thu, 23 Apr 2026 12:26:56 +0200 Subject: [PATCH] =?UTF-8?q?Fix=20#30:=20Verlaufsansicht=20=E2=80=93=20neue?= =?UTF-8?q?=20Momente=205=20s=20in=20Momente,=20dann=20in=20Verlauf?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Neue Logbuch-Momente (vergangene Treffen, Notizen) erscheinen nach dem Speichern 5 Sekunden mit 45 % Deckkraft in der Momente-Sektion und wandern dann animiert in den Verlauf. Aktive Momente (offene Vorhaben, Zukunfts- treffen) bleiben dauerhaft in der Momente-Sektion. Der Verlauf zeigt nur noch abgeschlossene/vergangene Einträge. Co-Authored-By: Claude Sonnet 4.6 --- nahbar/nahbar/PersonDetailView.swift | 53 +++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/nahbar/nahbar/PersonDetailView.swift b/nahbar/nahbar/PersonDetailView.swift index be640c1..3a2afa1 100644 --- a/nahbar/nahbar/PersonDetailView.swift +++ b/nahbar/nahbar/PersonDetailView.swift @@ -39,6 +39,10 @@ struct PersonDetailView: View { @State private var todoForEdit: Todo? = nil @State private var fadingOutTodos: [Todo] = [] + // Neu hinzugefügte Logbuch-Momente – 5 s in Momente sichtbar, dann in Verlauf + @State private var fadingOutMoments: [Moment] = [] + @State private var seenMomentIDs: Set = [] + // Kalender-Lösch-Bestätigung @State private var momentPendingDelete: Moment? = nil @State private var showCalendarDeleteDialog = false @@ -70,7 +74,7 @@ struct PersonDetailView: View { } momentsSection todosSection - if !person.sortedMoments.isEmpty || !person.sortedLogEntries.isEmpty { logbuchSection } + if !mergedLogPreview.isEmpty { logbuchSection } if hasInfoContent { infoSection } } .padding(.horizontal, 20) @@ -100,6 +104,26 @@ struct PersonDetailView: View { } } } + .onChange(of: showingAddMoment) { _, isShowing in + if isShowing { + seenMomentIDs = Set(person.sortedMoments.map(\.id)) + } else { + // Neu gespeicherte Logbuch-Momente (keine Vorhaben, kein Zukunftstreffen) kurz anzeigen + let newLogbuchMoments = person.sortedMoments.filter { moment in + guard !seenMomentIDs.contains(moment.id) else { return false } + let isActive = moment.isOpen || (moment.isMeeting && moment.createdAt > Date()) + return !isActive + } + for moment in newLogbuchMoments { + withAnimation { fadingOutMoments.append(moment) } + DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) { + withAnimation(.easeOut(duration: 0.35)) { + fadingOutMoments.removeAll { $0.id == moment.id } + } + } + } + } + } .sheet(isPresented: $showingEditPerson) { AddPersonView(existingPerson: person) } @@ -342,6 +366,18 @@ struct PersonDetailView: View { // MARK: - Momente + /// Aktive Momente: offene Vorhaben + noch ausstehende Treffen in der Zukunft. + private var activeMoments: [Moment] { + person.sortedMoments.filter { $0.isOpen || ($0.isMeeting && $0.createdAt > Date()) } + } + + /// Was in der Momente-Sektion angezeigt wird: aktive + kurzzeitig sichtbare neue Logbuch-Momente. + private var visibleMoments: [Moment] { + let fadingIDs = Set(fadingOutMoments.map(\.id)) + let active = activeMoments.filter { !fadingIDs.contains($0.id) } + return active + fadingOutMoments + } + private var momentsSection: some View { VStack(alignment: .leading, spacing: 10) { HStack { @@ -376,9 +412,9 @@ struct PersonDetailView: View { .font(.system(size: 14)) .foregroundStyle(theme.contentTertiary) .padding(.vertical, 4) - } else { + } else if !visibleMoments.isEmpty { VStack(spacing: 0) { - ForEach(Array(person.sortedMoments.enumerated()), id: \.element.id) { index, moment in + ForEach(Array(visibleMoments.enumerated()), id: \.element.id) { index, moment in VStack(spacing: 0) { MomentRowView( moment: moment, @@ -390,7 +426,8 @@ struct PersonDetailView: View { onEdit: { momentForTextEdit = moment }, onToggleImportant: { toggleImportant(moment) } ) - if index < person.sortedMoments.count - 1 { RowDivider() } + .opacity(fadingOutMoments.contains(where: { $0.id == moment.id }) ? 0.45 : 1.0) + if index < visibleMoments.count - 1 { RowDivider() } } } } @@ -460,7 +497,13 @@ struct PersonDetailView: View { } private var mergedLogPreview: [LogPreviewItem] { - let momentItems = person.sortedMoments.map { + // Nur Momente die weder aktiv (offene Vorhaben / Zukunftstreffen) noch gerade sichtbar ausklingend sind + let activeIDs = Set(activeMoments.map(\.id)) + let fadingIDs = Set(fadingOutMoments.map(\.id)) + let logbuchMoments = person.sortedMoments.filter { + !activeIDs.contains($0.id) && !fadingIDs.contains($0.id) + } + let momentItems = logbuchMoments.map { LogPreviewItem(id: "m-\($0.id)", icon: $0.type.icon, title: $0.text, typeLabel: $0.type.displayName, date: $0.createdAt) }