diff --git a/Coinly/Features/Accounts/AccountListView.swift b/Coinly/Features/Accounts/AccountListView.swift new file mode 100644 index 0000000..eb6dab8 --- /dev/null +++ b/Coinly/Features/Accounts/AccountListView.swift @@ -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 + } + } + } + } +} diff --git a/Coinly/Features/Accounts/AddAccountView.swift b/Coinly/Features/Accounts/AddAccountView.swift new file mode 100644 index 0000000..93d357a --- /dev/null +++ b/Coinly/Features/Accounts/AddAccountView.swift @@ -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) + } +} \ No newline at end of file diff --git a/Coinly/Features/Accounts/SelectAccountTypeView.swift b/Coinly/Features/Accounts/SelectAccountTypeView.swift new file mode 100644 index 0000000..a1b0e82 --- /dev/null +++ b/Coinly/Features/Accounts/SelectAccountTypeView.swift @@ -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" + } + } +} diff --git a/Coinly/Features/Models/AccountModel.swift b/Coinly/Features/Models/AccountModel.swift index a28e529..dd2f538 100644 --- a/Coinly/Features/Models/AccountModel.swift +++ b/Coinly/Features/Models/AccountModel.swift @@ -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()) ) ] } diff --git a/Coinly/UI/Components/AccountRowView.swift b/Coinly/UI/Components/AccountRowView.swift new file mode 100644 index 0000000..181ae6e --- /dev/null +++ b/Coinly/UI/Components/AccountRowView.swift @@ -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()) + } +} \ No newline at end of file