Allow viewing evolution chart for individual or multiple sources across categories

Adds multi-source selection to the evolution chart source filter.
Users can now tap multiple sources to compare their evolution side by side,
regardless of which category they belong to.

Fixes #24

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
alexandrev-tibco
2026-02-11 23:52:38 +01:00
parent 9d2ed68dcc
commit 0dac19b109
2 changed files with 20 additions and 6 deletions
@@ -86,6 +86,7 @@ class ChartsViewModel: ObservableObject {
@Published var selectedChartType: ChartType = .evolution @Published var selectedChartType: ChartType = .evolution
@Published var selectedCategory: Category? @Published var selectedCategory: Category?
@Published var selectedSource: InvestmentSource? @Published var selectedSource: InvestmentSource?
@Published var selectedSourceIds: Set<UUID> = []
@Published var selectedTimeRange: TimeRange = .year @Published var selectedTimeRange: TimeRange = .year
@Published var selectedAccount: Account? @Published var selectedAccount: Account?
@Published var showAllAccounts = true @Published var showAllAccounts = true
@@ -290,7 +291,12 @@ class ChartsViewModel: ObservableObject {
} }
let sources: [InvestmentSource] let sources: [InvestmentSource]
if let selectedSource { if !selectedSourceIds.isEmpty {
sources = sourceRepository.sources.filter { source in
guard let id = source.id else { return false }
return selectedSourceIds.contains(id) && shouldIncludeSource(source)
}
} else if let selectedSource {
sources = sourceRepository.sources.filter { $0.id == selectedSource.id && shouldIncludeSource($0) } sources = sourceRepository.sources.filter { $0.id == selectedSource.id && shouldIncludeSource($0) }
} else if let category = category { } else if let category = category {
sources = sourceRepository.fetchSources(for: category).filter { shouldIncludeSource($0) } sources = sourceRepository.fetchSources(for: category).filter { shouldIncludeSource($0) }
@@ -208,18 +208,19 @@ struct ChartsContainerView: View {
if availableSources.count > 1 { if availableSources.count > 1 {
Button { Button {
viewModel.selectedSource = nil viewModel.selectedSource = nil
viewModel.selectedSourceIds.removeAll()
} label: { } label: {
Text("All Sources") Text("All Sources")
.font(.caption.weight(.medium)) .font(.caption.weight(.medium))
.padding(.horizontal, 12) .padding(.horizontal, 12)
.padding(.vertical, 6) .padding(.vertical, 6)
.background( .background(
viewModel.selectedSource == nil viewModel.selectedSourceIds.isEmpty && viewModel.selectedSource == nil
? Color.appPrimary ? Color.appPrimary
: Color.gray.opacity(0.1) : Color.gray.opacity(0.1)
) )
.foregroundColor( .foregroundColor(
viewModel.selectedSource == nil viewModel.selectedSourceIds.isEmpty && viewModel.selectedSource == nil
? .white ? .white
: .primary : .primary
) )
@@ -228,21 +229,28 @@ struct ChartsContainerView: View {
} }
ForEach(availableSources, id: \.id) { source in ForEach(availableSources, id: \.id) { source in
let sourceId = source.id ?? UUID()
let isSelected = viewModel.selectedSourceIds.contains(sourceId)
Button { Button {
viewModel.selectedSource = source viewModel.selectedSource = nil
viewModel.selectedCategory = nil viewModel.selectedCategory = nil
if isSelected {
viewModel.selectedSourceIds.remove(sourceId)
} else {
viewModel.selectedSourceIds.insert(sourceId)
}
} label: { } label: {
Text(source.name) Text(source.name)
.font(.caption.weight(.medium)) .font(.caption.weight(.medium))
.padding(.horizontal, 12) .padding(.horizontal, 12)
.padding(.vertical, 6) .padding(.vertical, 6)
.background( .background(
viewModel.selectedSource?.id == source.id isSelected
? Color.appPrimary ? Color.appPrimary
: Color.gray.opacity(0.1) : Color.gray.opacity(0.1)
) )
.foregroundColor( .foregroundColor(
viewModel.selectedSource?.id == source.id isSelected
? .white ? .white
: .primary : .primary
) )