Base fixes and test harness

This commit is contained in:
alexandrev-tibco
2026-02-01 11:12:57 +01:00
parent f5b13ec924
commit e328767c4a
39 changed files with 3575 additions and 142 deletions
@@ -1,4 +1,5 @@
import SwiftUI
import CoreData
import Charts
struct SourceDetailView: View {
@@ -7,7 +8,8 @@ struct SourceDetailView: View {
@AppStorage("calmModeEnabled") private var calmModeEnabled = true
@State private var showingDeleteConfirmation = false
@State private var editingSnapshot: Snapshot?
@Environment(\.managedObjectContext) private var viewContext
@State private var editingSnapshotSelection: SnapshotSelection?
init(source: InvestmentSource, iapService: IAPService) {
_viewModel = StateObject(wrappedValue: SourceDetailViewModel(
@@ -17,6 +19,19 @@ struct SourceDetailView: View {
}
var body: some View {
Group {
if viewModel.isDeleted {
Color.clear
.onAppear {
dismiss()
}
} else {
contentView
}
}
}
private var contentView: some View {
ZStack {
AppBackground()
@@ -49,7 +64,7 @@ struct SourceDetailView: View {
.padding()
}
}
.navigationTitle(viewModel.source.name)
.navigationTitle(viewModel.safeSourceName)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
@@ -70,12 +85,16 @@ struct SourceDetailView: View {
}
}
}
.sheet(isPresented: $viewModel.showingAddSnapshot) {
AddSnapshotView(source: viewModel.source)
}
.sheet(item: $editingSnapshot) { snapshot in
.sheet(isPresented: $viewModel.showingAddSnapshot) {
AddSnapshotView(source: viewModel.source)
}
.sheet(item: $editingSnapshotSelection) { selection in
if let snapshot = try? viewContext.existingObject(with: selection.id) as? Snapshot {
AddSnapshotView(source: viewModel.source, snapshot: snapshot)
} else {
MissingSnapshotView()
}
}
.sheet(isPresented: $viewModel.showingAddTransaction) {
AddTransactionView { type, date, shares, price, amount, notes in
viewModel.addTransaction(
@@ -100,13 +119,16 @@ struct SourceDetailView: View {
titleVisibility: .visible
) {
Button("Delete", role: .destructive) {
// Delete and dismiss
let repository = InvestmentSourceRepository()
repository.deleteSource(viewModel.source)
viewModel.deleteSource()
dismiss()
}
} message: {
Text("This will permanently delete \(viewModel.source.name) and all its snapshots.")
Text("This will permanently delete \(viewModel.safeSourceName) and all its snapshots.")
}
.onChange(of: viewModel.isDeleted) { _, deleted in
if deleted {
dismiss()
}
}
}
@@ -426,25 +448,25 @@ struct SourceDetailView: View {
// Use LazyVStack for better performance with many snapshots
LazyVStack(spacing: 0) {
ForEach(viewModel.visibleSnapshots) { snapshot in
ForEach(viewModel.visibleSnapshots, id: \.objectID) { snapshot in
VStack(spacing: 0) {
SnapshotRowView(snapshot: snapshot, onEdit: {
editingSnapshot = snapshot
editingSnapshotSelection = SnapshotSelection(id: snapshot.objectID)
})
.contentShape(Rectangle())
.onTapGesture {
editingSnapshot = snapshot
editingSnapshotSelection = SnapshotSelection(id: snapshot.objectID)
}
.contextMenu {
Button {
editingSnapshot = snapshot
editingSnapshotSelection = SnapshotSelection(id: snapshot.objectID)
} label: {
Label("Edit", systemImage: "pencil")
}
}
.swipeActions(edge: .trailing) {
Button {
editingSnapshot = snapshot
editingSnapshotSelection = SnapshotSelection(id: snapshot.objectID)
} label: {
Label("Edit", systemImage: "pencil")
}
@@ -456,7 +478,7 @@ struct SourceDetailView: View {
}
}
if snapshot.id != viewModel.visibleSnapshots.last?.id {
if snapshot.objectID != viewModel.visibleSnapshots.last?.objectID {
Divider()
}
}
@@ -505,7 +527,7 @@ struct SnapshotRowView: View {
var body: some View {
HStack(spacing: 12) {
VStack(alignment: .leading, spacing: 2) {
Text(snapshot.date.friendlyDescription)
Text(snapshot.safeDate.friendlyDescription)
.font(.subheadline)
if let notes = snapshot.notes, !notes.isEmpty {
@@ -591,6 +613,27 @@ struct MetricChip: View {
}
}
private struct SnapshotSelection: Identifiable {
let id: NSManagedObjectID
}
private struct MissingSnapshotView: View {
var body: some View {
VStack(spacing: 12) {
Image(systemName: "exclamationmark.triangle")
.font(.system(size: 40))
.foregroundStyle(.orange)
Text("Snapshot not available")
.font(.headline)
Text("This snapshot was removed or is no longer accessible.")
.font(.subheadline)
.multilineTextAlignment(.center)
.foregroundStyle(.secondary)
}
.padding()
}
}
#Preview {
NavigationStack {
Text("Source Detail Preview")