Files
InvestmentTrackerApp/PortfolioJournal/Views/Accounts/AccountEditorView.swift
T
2026-01-19 14:40:43 +01:00

167 lines
5.9 KiB
Swift

import SwiftUI
struct AccountEditorView: View {
@Environment(\.dismiss) private var dismiss
@EnvironmentObject private var accountStore: AccountStore
let account: Account?
@State private var name = ""
@State private var currencyCode = AppSettings.getOrCreate(in: CoreDataStack.shared.viewContext).currency
@State private var inputMode: InputMode = .simple
@State private var notificationFrequency: NotificationFrequency = .monthly
@State private var customFrequencyMonths = 1
@State private var errorMessage: String?
private let accountRepository = AccountRepository()
var body: some View {
NavigationStack {
Form {
Section {
if isEditingDefaultAccount {
HStack {
Text(Account.defaultAccountName)
.foregroundColor(.secondary)
Spacer()
Image(systemName: "lock.fill")
.font(.caption)
.foregroundColor(.secondary)
}
} else {
TextField("Account name", text: $name)
}
Picker("Currency", selection: $currencyCode) {
ForEach(CurrencyPicker.commonCodes, id: \.self) { code in
Text(code).tag(code)
}
}
} header: {
Text("Account")
} footer: {
if let errorMessage {
Text(errorMessage)
.foregroundColor(.negativeRed)
} else if isEditingDefaultAccount {
Text("The Default account name cannot be changed.")
}
}
Section {
Picker("Input Mode", selection: $inputMode) {
ForEach(InputMode.allCases) { mode in
Text(mode.title).tag(mode)
}
}
} header: {
Text("Input")
} footer: {
Text(inputMode.description)
}
}
.navigationTitle(account == nil ? "New Account" : "Edit Account")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("Cancel") { dismiss() }
}
ToolbarItem(placement: .navigationBarTrailing) {
Button("Save") { saveAccount() }
.disabled(!isValid)
}
}
.onAppear {
loadAccount()
}
.onChange(of: name) { _, _ in
validateName()
}
}
.presentationDetents([.large])
}
private var isEditingDefaultAccount: Bool {
account?.isDefaultAccount == true
}
private var isValid: Bool {
if isEditingDefaultAccount {
return true // Can only edit currency/inputMode for Default account
}
let trimmed = name.trimmingCharacters(in: .whitespaces)
return !trimmed.isEmpty && errorMessage == nil
}
private func validateName() {
let trimmed = name.trimmingCharacters(in: .whitespaces)
if trimmed.isEmpty {
errorMessage = nil
return
}
// Check if trying to use reserved "Default" name for a non-default account
if trimmed.lowercased() == Account.defaultAccountName.lowercased() {
let isCreatingNew = account == nil
let isEditingNonDefault = account != nil && !account!.isDefaultAccount
if isCreatingNew || isEditingNonDefault {
errorMessage = "The name '\(Account.defaultAccountName)' is reserved."
return
}
}
// Check for duplicate names
if !accountRepository.isNameAvailable(trimmed, excludingAccountId: account?.safeId) {
errorMessage = "An account with this name already exists."
return
}
errorMessage = nil
}
private func loadAccount() {
guard let account else { return }
name = account.name
currencyCode = account.currencyCode ?? currencyCode
inputMode = InputMode(rawValue: account.inputMode) ?? .simple
notificationFrequency = .monthly
customFrequencyMonths = 1
validateName()
}
private func saveAccount() {
if !isEditingDefaultAccount {
validateName()
guard errorMessage == nil else { return }
}
let trimmedName = isEditingDefaultAccount ? Account.defaultAccountName : name.trimmingCharacters(in: .whitespaces)
let enforcedFrequency: NotificationFrequency = .monthly
let enforcedCustomMonths = 1
if let account {
accountRepository.updateAccount(
account,
name: isEditingDefaultAccount ? nil : trimmedName, // Don't update name for Default account
currency: currencyCode,
inputMode: inputMode,
notificationFrequency: enforcedFrequency,
customFrequencyMonths: enforcedCustomMonths
)
} else {
_ = accountRepository.createAccount(
name: trimmedName,
currency: currencyCode,
inputMode: inputMode,
notificationFrequency: enforcedFrequency,
customFrequencyMonths: enforcedCustomMonths
)
}
accountStore.persistSelection()
dismiss()
}
}
#Preview {
AccountEditorView(account: nil)
.environmentObject(AccountStore(iapService: IAPService()))
}