import SwiftUI struct AddSourceView: View { @Environment(\.dismiss) private var dismiss @EnvironmentObject var iapService: IAPService @State private var name = "" @State private var selectedCategory: Category? @State private var notificationFrequency: NotificationFrequency = .monthly @State private var customFrequencyMonths = 1 @State private var initialValue = "" @State private var showingCategoryPicker = false @StateObject private var categoryRepository = CategoryRepository() var body: some View { NavigationStack { Form { // Source Info Section { TextField("Source Name", text: $name) .textContentType(.organizationName) Button { showingCategoryPicker = true } label: { HStack { Text("Category") .foregroundColor(.primary) Spacer() if let category = selectedCategory { HStack(spacing: 6) { Image(systemName: category.icon) .foregroundColor(category.color) Text(category.name) .foregroundColor(.secondary) } } else { Text("Select") .foregroundColor(.secondary) } Image(systemName: "chevron.right") .font(.caption) .foregroundColor(.secondary) } } } header: { Text("Source Information") } // Initial Value (Optional) Section { HStack { Text("€") .foregroundColor(.secondary) TextField("0.00", text: $initialValue) .keyboardType(.decimalPad) } } header: { Text("Initial Value (Optional)") } footer: { Text("You can add snapshots later if you prefer.") } // Notification Settings Section { Picker("Reminder Frequency", selection: $notificationFrequency) { ForEach(NotificationFrequency.allCases) { frequency in Text(frequency.displayName).tag(frequency) } } if notificationFrequency == .custom { Stepper( "Every \(customFrequencyMonths) month\(customFrequencyMonths > 1 ? "s" : "")", value: $customFrequencyMonths, in: 1...24 ) } } header: { Text("Reminders") } footer: { Text("We'll remind you to update this investment based on your selected frequency.") } } .navigationTitle("Add Source") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarLeading) { Button("Cancel") { dismiss() } } ToolbarItem(placement: .navigationBarTrailing) { Button("Add") { saveSource() } .disabled(!isValid) .fontWeight(.semibold) } } .sheet(isPresented: $showingCategoryPicker) { CategoryPickerView( selectedCategory: $selectedCategory, categories: categoryRepository.categories ) } .onAppear { categoryRepository.createDefaultCategoriesIfNeeded() if selectedCategory == nil { selectedCategory = categoryRepository.categories.first } } } } // MARK: - Validation private var isValid: Bool { !name.trimmingCharacters(in: .whitespaces).isEmpty && selectedCategory != nil } // MARK: - Save private func saveSource() { guard let category = selectedCategory else { return } let repository = InvestmentSourceRepository() let source = repository.createSource( name: name.trimmingCharacters(in: .whitespaces), category: category, notificationFrequency: notificationFrequency, customFrequencyMonths: customFrequencyMonths ) // Add initial snapshot if provided if let value = parseDecimal(initialValue), value > 0 { let snapshotRepository = SnapshotRepository() snapshotRepository.createSnapshot( for: source, date: Date(), value: value ) } // Schedule notification NotificationService.shared.scheduleReminder(for: source) // Log analytics FirebaseService.shared.logSourceAdded( categoryName: category.name, sourceCount: repository.sourceCount ) dismiss() } private func parseDecimal(_ string: String) -> Decimal? { let cleaned = string .replacingOccurrences(of: "€", with: "") .replacingOccurrences(of: ",", with: ".") .trimmingCharacters(in: .whitespaces) guard !cleaned.isEmpty else { return nil } let formatter = NumberFormatter() formatter.numberStyle = .decimal formatter.locale = Locale(identifier: "en_US") return formatter.number(from: cleaned)?.decimalValue } } // MARK: - Category Picker View struct CategoryPickerView: View { @Environment(\.dismiss) private var dismiss @Binding var selectedCategory: Category? let categories: [Category] var body: some View { NavigationStack { List(categories) { category in Button { selectedCategory = category dismiss() } label: { HStack(spacing: 12) { ZStack { Circle() .fill(category.color.opacity(0.2)) .frame(width: 40, height: 40) Image(systemName: category.icon) .foregroundColor(category.color) } Text(category.name) .foregroundColor(.primary) Spacer() if selectedCategory?.id == category.id { Image(systemName: "checkmark") .foregroundColor(.appPrimary) } } } } .navigationTitle("Select Category") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button("Done") { dismiss() } } } } } } // MARK: - Edit Source View struct EditSourceView: View { @Environment(\.dismiss) private var dismiss let source: InvestmentSource @State private var name: String @State private var selectedCategory: Category? @State private var notificationFrequency: NotificationFrequency @State private var customFrequencyMonths: Int @State private var showingCategoryPicker = false @StateObject private var categoryRepository = CategoryRepository() init(source: InvestmentSource) { self.source = source _name = State(initialValue: source.name) _selectedCategory = State(initialValue: source.category) _notificationFrequency = State(initialValue: source.frequency) _customFrequencyMonths = State(initialValue: Int(source.customFrequencyMonths)) } var body: some View { NavigationStack { Form { Section { TextField("Source Name", text: $name) Button { showingCategoryPicker = true } label: { HStack { Text("Category") .foregroundColor(.primary) Spacer() if let category = selectedCategory { HStack(spacing: 6) { Image(systemName: category.icon) .foregroundColor(category.color) Text(category.name) .foregroundColor(.secondary) } } Image(systemName: "chevron.right") .font(.caption) .foregroundColor(.secondary) } } } Section { Picker("Reminder Frequency", selection: $notificationFrequency) { ForEach(NotificationFrequency.allCases) { frequency in Text(frequency.displayName).tag(frequency) } } if notificationFrequency == .custom { Stepper( "Every \(customFrequencyMonths) month\(customFrequencyMonths > 1 ? "s" : "")", value: $customFrequencyMonths, in: 1...24 ) } } header: { Text("Reminders") } } .navigationTitle("Edit Source") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarLeading) { Button("Cancel") { dismiss() } } ToolbarItem(placement: .navigationBarTrailing) { Button("Save") { saveChanges() } .disabled(!isValid) .fontWeight(.semibold) } } .sheet(isPresented: $showingCategoryPicker) { CategoryPickerView( selectedCategory: $selectedCategory, categories: categoryRepository.categories ) } } } private var isValid: Bool { !name.trimmingCharacters(in: .whitespaces).isEmpty && selectedCategory != nil } private func saveChanges() { guard let category = selectedCategory else { return } let repository = InvestmentSourceRepository() repository.updateSource( source, name: name.trimmingCharacters(in: .whitespaces), category: category, notificationFrequency: notificationFrequency, customFrequencyMonths: customFrequencyMonths ) // Reschedule notification NotificationService.shared.scheduleReminder(for: source) dismiss() } } // MARK: - Add Snapshot View struct AddSnapshotView: View { @Environment(\.dismiss) private var dismiss let source: InvestmentSource @StateObject private var viewModel: SnapshotFormViewModel init(source: InvestmentSource) { self.source = source _viewModel = StateObject(wrappedValue: SnapshotFormViewModel(source: source)) } var body: some View { NavigationStack { Form { Section { DatePicker( "Date", selection: $viewModel.date, in: ...Date(), displayedComponents: .date ) HStack { Text("€") .foregroundColor(.secondary) TextField("Value", text: $viewModel.valueString) .keyboardType(.decimalPad) } if let previous = viewModel.previousValueString { Text(previous) .font(.caption) .foregroundColor(.secondary) } } header: { Text("Snapshot Details") } // Change preview if let change = viewModel.formattedChange, let percentage = viewModel.formattedChangePercentage { Section { HStack { Text("Change from previous") Spacer() Text("\(change) (\(percentage))") .foregroundColor( (viewModel.changeFromPrevious ?? 0) >= 0 ? .positiveGreen : .negativeRed ) } } } Section { Toggle("Include Contribution", isOn: $viewModel.includeContribution) if viewModel.includeContribution { HStack { Text("€") .foregroundColor(.secondary) TextField("New capital added", text: $viewModel.contributionString) .keyboardType(.decimalPad) } } } header: { Text("Contribution (Optional)") } footer: { Text("Track new capital you've added to separate it from investment growth.") } Section { TextField("Notes", text: $viewModel.notes, axis: .vertical) .lineLimit(3...6) } header: { Text("Notes (Optional)") } } .navigationTitle(viewModel.title) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarLeading) { Button("Cancel") { dismiss() } } ToolbarItem(placement: .navigationBarTrailing) { Button(viewModel.buttonTitle) { saveSnapshot() } .disabled(!viewModel.isValid) .fontWeight(.semibold) } } } } private func saveSnapshot() { guard let value = viewModel.value else { return } let repository = SnapshotRepository() repository.createSnapshot( for: source, date: viewModel.date, value: value, contribution: viewModel.contribution, notes: viewModel.notes.isEmpty ? nil : viewModel.notes ) // Reschedule notification NotificationService.shared.scheduleReminder(for: source) // Log analytics FirebaseService.shared.logSnapshotAdded(sourceName: source.name, value: value) dismiss() } } #Preview { AddSourceView() .environmentObject(IAPService()) }