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) }