Fix account types: rename cash to wallet, update all related views and fix type references
This commit is contained in:
parent
79e649756a
commit
072c31192b
5 changed files with 443 additions and 49 deletions
91
Coinly/Features/Accounts/AccountListView.swift
Normal file
91
Coinly/Features/Accounts/AccountListView.swift
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
//
|
||||
// AccountListView.swift
|
||||
// Coinly
|
||||
//
|
||||
// Created by Vadym Samoilenko on 02/03/2025.
|
||||
//
|
||||
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct AccountListView: View {
|
||||
@EnvironmentObject private var settings: AppSettings
|
||||
@State private var showingAddAccount = false
|
||||
@State private var selectedAccountType: AccountType?
|
||||
|
||||
private let accounts = [
|
||||
AccountModel(
|
||||
name: "Cash Wallet",
|
||||
type: .wallet,
|
||||
currency: .usd,
|
||||
balance: 1000
|
||||
),
|
||||
AccountModel(
|
||||
name: "Main Bank Account",
|
||||
type: .bankAccount,
|
||||
currency: .usd,
|
||||
balance: 5000
|
||||
),
|
||||
AccountModel(
|
||||
name: "Credit Card",
|
||||
type: .creditCard,
|
||||
currency: .usd,
|
||||
balance: 2000,
|
||||
creditLimit: 10000,
|
||||
interestRate: 19.99,
|
||||
dueDate: Calendar.current.date(byAdding: .day, value: 15, to: Date())
|
||||
)
|
||||
]
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
ForEach(AccountType.allCases, id: \.self) { type in
|
||||
Section {
|
||||
let typeAccounts = accounts.filter { $0.type == type }
|
||||
if typeAccounts.isEmpty {
|
||||
Button {
|
||||
selectedAccountType = type
|
||||
showingAddAccount = true
|
||||
} label: {
|
||||
HStack {
|
||||
Image(systemName: "plus.circle.fill")
|
||||
.foregroundColor(.accentColor)
|
||||
Text("Add \(type.rawValue)")
|
||||
.foregroundColor(.accentColor)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ForEach(typeAccounts) { account in
|
||||
AccountRowView(account: account)
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
HStack {
|
||||
Image(systemName: type.icon)
|
||||
.foregroundColor(type.color)
|
||||
Text(type.rawValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Accounts")
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button {
|
||||
showingAddAccount = true
|
||||
} label: {
|
||||
Image(systemName: "plus")
|
||||
}
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $showingAddAccount) {
|
||||
if let type = selectedAccountType {
|
||||
AddAccountView(accountType: type)
|
||||
} else {
|
||||
SelectAccountTypeView { selectedType in
|
||||
selectedAccountType = selectedType
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
128
Coinly/Features/Accounts/AddAccountView.swift
Normal file
128
Coinly/Features/Accounts/AddAccountView.swift
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
//
|
||||
// AddAccountView.swift
|
||||
// Coinly
|
||||
//
|
||||
// Created by Vadym Samoilenko on 02/03/2025.
|
||||
//
|
||||
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct AddAccountView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@EnvironmentObject private var settings: AppSettings
|
||||
|
||||
let accountType: AccountType
|
||||
|
||||
@State private var name = ""
|
||||
@State private var balance = ""
|
||||
@State private var currency: AppSettings.Currency = .usd
|
||||
@State private var isActive = true
|
||||
|
||||
// Credit Card fields
|
||||
@State private var creditLimit = ""
|
||||
@State private var interestRate = ""
|
||||
@State private var dueDate = Date()
|
||||
@State private var minimumPayment = ""
|
||||
|
||||
// Deposit fields
|
||||
@State private var depositEndDate = Date()
|
||||
@State private var depositInterestRate = ""
|
||||
@State private var isAutoRenewable = false
|
||||
|
||||
// Debt fields
|
||||
@State private var creditorName = ""
|
||||
@State private var debtInterestRate = ""
|
||||
|
||||
private var isValid: Bool {
|
||||
!name.isEmpty && !balance.isEmpty
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Form {
|
||||
Section("Basic Information") {
|
||||
TextField("Account Name", text: $name)
|
||||
|
||||
HStack {
|
||||
Text(settings.currency.symbol)
|
||||
.foregroundColor(.secondary)
|
||||
TextField("Balance", text: $balance)
|
||||
.keyboardType(.decimalPad)
|
||||
}
|
||||
|
||||
Picker("Currency", selection: $currency) {
|
||||
ForEach(AppSettings.Currency.allCases, id: \.self) { currency in
|
||||
Text(currency.rawValue).tag(currency)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if accountType == .creditCard {
|
||||
Section("Credit Card Details") {
|
||||
HStack {
|
||||
Text(settings.currency.symbol)
|
||||
TextField("Credit Limit", text: $creditLimit)
|
||||
.keyboardType(.decimalPad)
|
||||
}
|
||||
|
||||
TextField("Interest Rate %", text: $interestRate)
|
||||
.keyboardType(.decimalPad)
|
||||
|
||||
DatePicker("Due Date", selection: $dueDate, displayedComponents: .date)
|
||||
|
||||
HStack {
|
||||
Text(settings.currency.symbol)
|
||||
TextField("Minimum Payment", text: $minimumPayment)
|
||||
.keyboardType(.decimalPad)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if accountType == .deposit {
|
||||
Section("Deposit Details") {
|
||||
DatePicker("End Date", selection: $depositEndDate, displayedComponents: .date)
|
||||
|
||||
TextField("Interest Rate %", text: $depositInterestRate)
|
||||
.keyboardType(.decimalPad)
|
||||
|
||||
Toggle("Auto-renewable", isOn: $isAutoRenewable)
|
||||
}
|
||||
}
|
||||
|
||||
if accountType == .debt {
|
||||
Section("Debt Details") {
|
||||
TextField("Creditor Name", text: $creditorName)
|
||||
|
||||
TextField("Interest Rate %", text: $debtInterestRate)
|
||||
.keyboardType(.decimalPad)
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("New \(accountType.rawValue)")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button("Cancel") {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button("Add") {
|
||||
// TODO: Add account creation
|
||||
dismiss()
|
||||
}
|
||||
.disabled(!isValid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AddAccountView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
AddAccountView(accountType: .creditCard)
|
||||
.environmentObject(AppSettings.shared)
|
||||
}
|
||||
}
|
||||
72
Coinly/Features/Accounts/SelectAccountTypeView.swift
Normal file
72
Coinly/Features/Accounts/SelectAccountTypeView.swift
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
//
|
||||
// SelectAccountTypeView.swift
|
||||
// Coinly
|
||||
//
|
||||
// Created by Vadym Samoilenko on 02/03/2025.
|
||||
//
|
||||
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SelectAccountTypeView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
let onSelect: (AccountType) -> Void
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
List {
|
||||
ForEach(AccountType.allCases, id: \.self) { type in
|
||||
Button {
|
||||
onSelect(type)
|
||||
dismiss()
|
||||
} label: {
|
||||
HStack {
|
||||
Image(systemName: type.icon)
|
||||
.foregroundColor(type.color)
|
||||
.frame(width: 30)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text(type.rawValue)
|
||||
.font(.headline)
|
||||
.foregroundColor(.primary)
|
||||
|
||||
Text(descriptionFor(type))
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "chevron.right")
|
||||
.foregroundColor(Color(uiColor: .systemGray4))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Select Account Type")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button("Cancel") {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func descriptionFor(_ type: AccountType) -> String {
|
||||
switch type {
|
||||
case .wallet:
|
||||
return "Cash and physical money"
|
||||
case .bankAccount:
|
||||
return "Regular bank account"
|
||||
case .creditCard:
|
||||
return "Credit card with limit"
|
||||
case .deposit:
|
||||
return "Savings and deposits"
|
||||
case .debt:
|
||||
return "Debts and loans"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +1,30 @@
|
|||
//
|
||||
// AccountType.swift
|
||||
// Coinly
|
||||
//
|
||||
// Created by Vadym Samoilenko on 02/03/2025.
|
||||
//
|
||||
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
enum AccountType: String, Codable, CaseIterable {
|
||||
case wallet = "Wallet" // Наличные/кошелек
|
||||
case bankAccount = "Bank" // Банковский счет
|
||||
case creditCard = "Credit" // Кредитная карта
|
||||
case deposit = "Deposit" // Депозит
|
||||
case debt = "Debt" // Долг
|
||||
case wallet = "Wallet"
|
||||
case bankAccount = "Bank Account"
|
||||
case creditCard = "Credit Card"
|
||||
case deposit = "Deposit"
|
||||
case debt = "Debt"
|
||||
|
||||
var icon: String {
|
||||
switch self {
|
||||
case .wallet: return "wallet.pass"
|
||||
case .bankAccount: return "building.columns"
|
||||
case .creditCard: return "creditcard"
|
||||
case .deposit: return "banknote"
|
||||
case .debt: return "exclamationmark.circle"
|
||||
case .wallet: return "banknote"
|
||||
case .bankAccount: return "building.columns.fill"
|
||||
case .creditCard: return "creditcard.fill"
|
||||
case .deposit: return "vault.fill"
|
||||
case .debt: return "exclamationmark.circle.fill"
|
||||
}
|
||||
}
|
||||
|
||||
var color: Color {
|
||||
switch self {
|
||||
case .wallet: return Color(uiColor: .systemGreen)
|
||||
case .bankAccount: return Color(uiColor: .systemBlue)
|
||||
case .creditCard: return Color(uiColor: .systemPurple)
|
||||
case .deposit: return Color(uiColor: .systemOrange)
|
||||
case .debt: return Color(uiColor: .systemRed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -32,51 +35,57 @@ struct AccountModel: Identifiable, Codable {
|
|||
var type: AccountType
|
||||
var currency: AppSettings.Currency
|
||||
var balance: Double
|
||||
var initialBalance: Double
|
||||
var isActive: Bool
|
||||
|
||||
// Для кредитных карт
|
||||
// Кредитная карта
|
||||
var creditLimit: Double?
|
||||
var interestRate: Double?
|
||||
var dueDate: Date?
|
||||
var minimumPayment: Double?
|
||||
|
||||
// Для депозитов
|
||||
var depositInterestRate: Double?
|
||||
// Депозит
|
||||
var depositEndDate: Date?
|
||||
var depositInterestRate: Double?
|
||||
var isAutoRenewable: Bool?
|
||||
|
||||
// Для долгов
|
||||
var debtInterestRate: Double?
|
||||
var debtDueDate: Date?
|
||||
// Долг
|
||||
var creditorName: String?
|
||||
var debtInterestRate: Double?
|
||||
var paymentSchedule: [DebtPayment]?
|
||||
|
||||
init(
|
||||
name: String,
|
||||
type: AccountType,
|
||||
currency: AppSettings.Currency,
|
||||
balance: Double,
|
||||
initialBalance: Double = 0,
|
||||
balance: Double = 0,
|
||||
isActive: Bool = true,
|
||||
creditLimit: Double? = nil,
|
||||
interestRate: Double? = nil,
|
||||
dueDate: Date? = nil,
|
||||
depositInterestRate: Double? = nil,
|
||||
minimumPayment: Double? = nil,
|
||||
depositEndDate: Date? = nil,
|
||||
depositInterestRate: Double? = nil,
|
||||
isAutoRenewable: Bool? = nil,
|
||||
creditorName: String? = nil,
|
||||
debtInterestRate: Double? = nil,
|
||||
debtDueDate: Date? = nil,
|
||||
creditorName: String? = nil
|
||||
paymentSchedule: [DebtPayment]? = nil
|
||||
) {
|
||||
self.id = UUID().uuidString
|
||||
self.name = name
|
||||
self.type = type
|
||||
self.currency = currency
|
||||
self.balance = balance
|
||||
self.initialBalance = initialBalance
|
||||
self.isActive = isActive
|
||||
self.creditLimit = creditLimit
|
||||
self.interestRate = interestRate
|
||||
self.dueDate = dueDate
|
||||
self.depositInterestRate = depositInterestRate
|
||||
self.minimumPayment = minimumPayment
|
||||
self.depositEndDate = depositEndDate
|
||||
self.debtInterestRate = debtInterestRate
|
||||
self.debtDueDate = debtDueDate
|
||||
self.depositInterestRate = depositInterestRate
|
||||
self.isAutoRenewable = isAutoRenewable
|
||||
self.creditorName = creditorName
|
||||
self.debtInterestRate = debtInterestRate
|
||||
self.paymentSchedule = paymentSchedule
|
||||
}
|
||||
|
||||
// Вычисляемые свойства
|
||||
|
|
@ -86,40 +95,70 @@ struct AccountModel: Identifiable, Codable {
|
|||
}
|
||||
|
||||
var isOverdue: Bool {
|
||||
guard let dueDate = dueDate ?? debtDueDate else { return false }
|
||||
guard let dueDate = dueDate else { return false }
|
||||
return Date() > dueDate
|
||||
}
|
||||
|
||||
// Sample Data
|
||||
var formattedBalance: String {
|
||||
balance.formatAsCurrency()
|
||||
}
|
||||
|
||||
// Методы для работы с кредитной картой
|
||||
mutating func addPurchase(_ amount: Double) -> Bool {
|
||||
guard type == .creditCard,
|
||||
let limit = creditLimit,
|
||||
balance + amount <= limit else {
|
||||
return false
|
||||
}
|
||||
balance += amount
|
||||
return true
|
||||
}
|
||||
|
||||
mutating func makePayment(_ amount: Double) -> Bool {
|
||||
guard amount <= balance else { return false }
|
||||
balance -= amount
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Структура для платежей по долгу
|
||||
struct DebtPayment: Codable, Identifiable {
|
||||
let id: String
|
||||
var dueDate: Date
|
||||
var amount: Double
|
||||
var isPaid: Bool
|
||||
|
||||
init(dueDate: Date, amount: Double, isPaid: Bool = false) {
|
||||
self.id = UUID().uuidString
|
||||
self.dueDate = dueDate
|
||||
self.amount = amount
|
||||
self.isPaid = isPaid
|
||||
}
|
||||
}
|
||||
|
||||
// Sample Data
|
||||
extension AccountModel {
|
||||
static let sampleData = [
|
||||
AccountModel(
|
||||
name: "Cash Wallet",
|
||||
type: .wallet,
|
||||
currency: .usd,
|
||||
balance: 500
|
||||
balance: 1000
|
||||
),
|
||||
AccountModel(
|
||||
name: "Main Bank Account",
|
||||
type: .bankAccount,
|
||||
currency: .usd,
|
||||
balance: 2500
|
||||
balance: 5000
|
||||
),
|
||||
AccountModel(
|
||||
name: "Credit Card",
|
||||
type: .creditCard,
|
||||
currency: .usd,
|
||||
balance: 1000,
|
||||
creditLimit: 5000,
|
||||
interestRate: 19.9,
|
||||
balance: 2000,
|
||||
creditLimit: 10000,
|
||||
interestRate: 19.99,
|
||||
dueDate: Calendar.current.date(byAdding: .day, value: 15, to: Date())
|
||||
),
|
||||
AccountModel(
|
||||
name: "Savings Deposit",
|
||||
type: .deposit,
|
||||
currency: .usd,
|
||||
balance: 10000,
|
||||
depositInterestRate: 5.5,
|
||||
depositEndDate: Calendar.current.date(byAdding: .month, value: 12, to: Date())
|
||||
)
|
||||
]
|
||||
}
|
||||
|
|
|
|||
64
Coinly/UI/Components/AccountRowView.swift
Normal file
64
Coinly/UI/Components/AccountRowView.swift
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
//
|
||||
// AccountRowView.swift
|
||||
// Coinly
|
||||
//
|
||||
// Created by Vadym Samoilenko on 02/03/2025.
|
||||
//
|
||||
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct AccountRowView: View {
|
||||
let account: AccountModel
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 8) {
|
||||
// Header
|
||||
HStack {
|
||||
Text(account.name)
|
||||
.font(.headline)
|
||||
Spacer()
|
||||
Text(account.currency.symbol)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
// Balance
|
||||
HStack {
|
||||
Text(account.formattedBalance)
|
||||
.font(.title3)
|
||||
.fontWeight(.semibold)
|
||||
Spacer()
|
||||
|
||||
if let availableCredit = account.availableCredit {
|
||||
Text("Available: \(availableCredit.formatAsCurrency())")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
// Additional Info
|
||||
if account.type == .creditCard {
|
||||
HStack {
|
||||
if let dueDate = account.dueDate {
|
||||
Label(dueDate.formatted(date: .abbreviated, time: .omitted),
|
||||
systemImage: "calendar")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
if let rate = account.interestRate {
|
||||
Text("•")
|
||||
.foregroundColor(.secondary)
|
||||
Label("\(String(format: "%.1f", rate))%",
|
||||
systemImage: "percent")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue