Fix #30: Verlaufsansicht – neue Momente 5 s in Momente, dann in Verlauf
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 <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,10 @@ struct PersonDetailView: View {
|
|||||||
@State private var todoForEdit: Todo? = nil
|
@State private var todoForEdit: Todo? = nil
|
||||||
@State private var fadingOutTodos: [Todo] = []
|
@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<UUID> = []
|
||||||
|
|
||||||
// Kalender-Lösch-Bestätigung
|
// Kalender-Lösch-Bestätigung
|
||||||
@State private var momentPendingDelete: Moment? = nil
|
@State private var momentPendingDelete: Moment? = nil
|
||||||
@State private var showCalendarDeleteDialog = false
|
@State private var showCalendarDeleteDialog = false
|
||||||
@@ -70,7 +74,7 @@ struct PersonDetailView: View {
|
|||||||
}
|
}
|
||||||
momentsSection
|
momentsSection
|
||||||
todosSection
|
todosSection
|
||||||
if !person.sortedMoments.isEmpty || !person.sortedLogEntries.isEmpty { logbuchSection }
|
if !mergedLogPreview.isEmpty { logbuchSection }
|
||||||
if hasInfoContent { infoSection }
|
if hasInfoContent { infoSection }
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 20)
|
.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) {
|
.sheet(isPresented: $showingEditPerson) {
|
||||||
AddPersonView(existingPerson: person)
|
AddPersonView(existingPerson: person)
|
||||||
}
|
}
|
||||||
@@ -342,6 +366,18 @@ struct PersonDetailView: View {
|
|||||||
|
|
||||||
// MARK: - Momente
|
// 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 {
|
private var momentsSection: some View {
|
||||||
VStack(alignment: .leading, spacing: 10) {
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
HStack {
|
HStack {
|
||||||
@@ -376,9 +412,9 @@ struct PersonDetailView: View {
|
|||||||
.font(.system(size: 14))
|
.font(.system(size: 14))
|
||||||
.foregroundStyle(theme.contentTertiary)
|
.foregroundStyle(theme.contentTertiary)
|
||||||
.padding(.vertical, 4)
|
.padding(.vertical, 4)
|
||||||
} else {
|
} else if !visibleMoments.isEmpty {
|
||||||
VStack(spacing: 0) {
|
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) {
|
VStack(spacing: 0) {
|
||||||
MomentRowView(
|
MomentRowView(
|
||||||
moment: moment,
|
moment: moment,
|
||||||
@@ -390,7 +426,8 @@ struct PersonDetailView: View {
|
|||||||
onEdit: { momentForTextEdit = moment },
|
onEdit: { momentForTextEdit = moment },
|
||||||
onToggleImportant: { toggleImportant(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] {
|
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,
|
LogPreviewItem(id: "m-\($0.id)", icon: $0.type.icon, title: $0.text,
|
||||||
typeLabel: $0.type.displayName, date: $0.createdAt)
|
typeLabel: $0.type.displayName, date: $0.createdAt)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user