138 lines
4.6 KiB
Swift
138 lines
4.6 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 {
|
|
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)
|
|
}
|
|
}
|
|
|
|
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 isValid: Bool {
|
|
let trimmed = name.trimmingCharacters(in: .whitespaces)
|
|
return !trimmed.isEmpty && !isDuplicateName(trimmed)
|
|
}
|
|
|
|
private func validateName() {
|
|
let trimmed = name.trimmingCharacters(in: .whitespaces)
|
|
if trimmed.isEmpty {
|
|
errorMessage = nil
|
|
return
|
|
}
|
|
errorMessage = isDuplicateName(trimmed) ? "An account with this name already exists." : nil
|
|
}
|
|
|
|
private func isDuplicateName(_ trimmed: String) -> Bool {
|
|
let normalized = trimmed.lowercased()
|
|
return accountRepository.accounts.contains { existing in
|
|
if let account, existing.id == account.id {
|
|
return false
|
|
}
|
|
return (existing.name).trimmingCharacters(in: .whitespaces).lowercased() == normalized
|
|
}
|
|
}
|
|
|
|
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() {
|
|
validateName()
|
|
guard errorMessage == nil else { return }
|
|
let trimmedName = name.trimmingCharacters(in: .whitespaces)
|
|
let enforcedFrequency: NotificationFrequency = .monthly
|
|
let enforcedCustomMonths = 1
|
|
if let account {
|
|
accountRepository.updateAccount(
|
|
account,
|
|
name: trimmedName,
|
|
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()))
|
|
}
|