diff --git a/Coinly/App/ContentView.swift b/Coinly/App/ContentView.swift index 304b4fe..1bf32ab 100644 --- a/Coinly/App/ContentView.swift +++ b/Coinly/App/ContentView.swift @@ -1,43 +1,41 @@ import SwiftUI struct ContentView: View { + @StateObject private var accountsStore = AccountsStore.shared + @StateObject private var settings = AppSettings.shared + @StateObject private var transactionsStore = TransactionsStore.shared + @StateObject private var categoryStore = CategoryStore.shared + var body: some View { TabView { NavigationView { DashboardView() } .tabItem { - Label("Dashboard", systemImage: "chart.pie") + Label("Dashboard", systemImage: "chart.pie.fill") } NavigationView { - AccountListView() + AccountsListView() } .tabItem { - Label("Accounts", systemImage: "creditcard") - } - - NavigationView { - CategoryListView(type: .expense) - } - .tabItem { - Label("Categories", systemImage: "tag") + Label("Accounts", systemImage: "creditcard.fill") } NavigationView { ProfileView() } .tabItem { - Label("Profile", systemImage: "person") + Label("Profile", systemImage: "person.fill") } } + .environmentObject(accountsStore) + .environmentObject(settings) + .environmentObject(transactionsStore) + .environmentObject(categoryStore) } } #Preview { ContentView() - .environmentObject(TransactionsStore.shared) - .environmentObject(AccountsStore.shared) - .environmentObject(CategoryStore.shared) - .environmentObject(AppSettings.shared) } diff --git a/Coinly/Features/Accounts/AccountCardView.swift b/Coinly/Features/Accounts/AccountCardView.swift index 555427d..566c7fd 100644 --- a/Coinly/Features/Accounts/AccountCardView.swift +++ b/Coinly/Features/Accounts/AccountCardView.swift @@ -1,6 +1,7 @@ import SwiftUI struct AccountCardView: View { + @EnvironmentObject private var accountsStore: AccountsStore let account: AccountModel let isSelected: Bool @@ -9,18 +10,12 @@ struct AccountCardView: View { HStack { Image(systemName: account.icon) .font(.title2) - .foregroundColor(account.type.color) Spacer() - if account.isDefault { - Text("Default") - .font(.caption2) - .foregroundColor(.secondary) - .padding(.horizontal, 8) - .padding(.vertical, 4) - .background(Color(uiColor: .systemGray6)) - .cornerRadius(8) + if account.id == accountsStore.defaultAccountId { + Image(systemName: "star.fill") + .foregroundColor(.yellow) } } @@ -29,33 +24,22 @@ struct AccountCardView: View { .font(.headline) Text(account.type.rawValue) - .font(.subheadline) + .font(.caption) .foregroundColor(.secondary) } Text(account.balance.formatted(.currency(code: account.currency.rawValue))) - .font(.title2.bold()) + .font(.title3.bold()) } .padding() - .frame(width: 200) - .background( - RoundedRectangle(cornerRadius: 12) - .fill(Color(uiColor: .systemBackground)) - .shadow( - color: isSelected ? Color.accentColor.opacity(0.3) : Color.black.opacity(0.1), - radius: isSelected ? 8 : 4 - ) - ) - .overlay( - RoundedRectangle(cornerRadius: 12) - .stroke(isSelected ? Color.accentColor : Color.clear, lineWidth: 2) - ) + .frame(width: 160) + .background(isSelected ? Color.accentColor.opacity(0.2) : Color(uiColor: .secondarySystemBackground)) + .cornerRadius(12) } } #Preview { - AccountCardView( - account: AccountModel.previewData, - isSelected: true - ) + AccountCardView(account: AccountModel.previewData, isSelected: false) + .environmentObject(AccountsStore.shared) + .padding() } diff --git a/Coinly/Features/Accounts/AccountListView.swift b/Coinly/Features/Accounts/AccountListView.swift deleted file mode 100644 index 5457ff8..0000000 --- a/Coinly/Features/Accounts/AccountListView.swift +++ /dev/null @@ -1,46 +0,0 @@ -import SwiftUI - -struct AccountListView: View { - @EnvironmentObject private var accountsStore: AccountsStore - @State private var showingAddAccount = false - - var body: some View { - List { - ForEach(accountsStore.accounts) { account in - NavigationLink(destination: AccountDetailView(account: account)) { - AccountRowView(account: account) - } - } - .onDelete(perform: deleteAccount) - } - .navigationTitle("Accounts") - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - Button { - showingAddAccount = true - } label: { - Image(systemName: "plus") - } - } - } - .sheet(isPresented: $showingAddAccount) { - NavigationView { - AddAccountView() - } - } - } - - private func deleteAccount(at offsets: IndexSet) { - offsets.forEach { index in - accountsStore.deleteAccount(accountsStore.accounts[index]) - } - } -} - -#Preview { - NavigationView { - AccountListView() - .environmentObject(AccountsStore.shared) - .environmentObject(AppSettings.shared) - } -} diff --git a/Coinly/Features/Accounts/AccountsListView.swift b/Coinly/Features/Accounts/AccountsListView.swift new file mode 100644 index 0000000..b560aac --- /dev/null +++ b/Coinly/Features/Accounts/AccountsListView.swift @@ -0,0 +1,90 @@ +import SwiftUI + +struct AccountsListView: View { + @EnvironmentObject private var accountsStore: AccountsStore + @EnvironmentObject private var settings: AppSettings + @State private var showingAddAccount = false + + var body: some View { + List { + if !accountsStore.accounts.isEmpty { + Section { + ForEach(accountsStore.accounts) { account in + NavigationLink(destination: AccountDetailView(account: account)) { + HStack { + Image(systemName: account.icon) + .font(.title2) + .frame(width: 32) + + VStack(alignment: .leading, spacing: 4) { + HStack { + Text(account.name) + .font(.headline) + + if account.id == accountsStore.defaultAccountId { + Image(systemName: "star.fill") + .foregroundColor(.yellow) + .font(.caption) + } + } + + Text(account.type.rawValue) + .font(.caption) + .foregroundColor(.secondary) + } + + Spacer() + + Text(account.balance.formatted(.currency(code: account.currency.rawValue))) + .font(.callout.bold()) + } + .padding(.vertical, 8) + } + } + } header: { + HStack { + Text("Total Balance") + Spacer() + Text(accountsStore.totalBalance.formatted(.currency(code: settings.selectedCurrency.rawValue))) + } + .textCase(nil) + .font(.headline) + .foregroundColor(.primary) + .padding(.vertical, 8) + } + } + } + .navigationTitle("Accounts") + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button { + showingAddAccount = true + } label: { + Image(systemName: "plus.circle.fill") + } + } + } + .sheet(isPresented: $showingAddAccount) { + NavigationView { + AddAccountView() + } + } + .overlay { + if accountsStore.accounts.isEmpty { + ContentUnavailableView( + "No Accounts", + systemImage: "creditcard", + description: Text("Add your first account to start tracking your finances") + ) + } + } + } +} + +#Preview { + NavigationView { + AccountsListView() + .environmentObject(AccountsStore.shared) + .environmentObject(AppSettings.shared) + } +} diff --git a/Coinly/Features/Accounts/AddAccountView.swift b/Coinly/Features/Accounts/AddAccountView.swift index 86e5246..a8d03e5 100644 --- a/Coinly/Features/Accounts/AddAccountView.swift +++ b/Coinly/Features/Accounts/AddAccountView.swift @@ -1,5 +1,3 @@ -// Path: Features/Accounts/AddAccountView.swift - import SwiftUI struct AddAccountView: View { @@ -8,30 +6,19 @@ struct AddAccountView: View { @Environment(\.dismiss) private var dismiss @State private var name = "" - @State private var type: AccountType = .cash - @State private var balance: Double = 0 - @State private var currency: AppSettings.Currency = .usd - @State private var icon = "creditcard" + @State private var type = AccountType.cash + @State private var balance = 0.0 + @State private var currency = AppSettings.Currency.usd @State private var isDefault = false - @State private var showingTypeSelector = false var body: some View { Form { Section { TextField("Account Name", text: $name) - Button { - showingTypeSelector = true - } label: { - HStack { - Text("Type") - Spacer() - HStack { - Image(systemName: type.icon) - .foregroundColor(type.color) - Text(type.rawValue) - .foregroundColor(.secondary) - } + Picker("Type", selection: $type) { + ForEach(AccountType.allCases, id: \.self) { type in + Text(type.rawValue).tag(type) } } @@ -51,19 +38,13 @@ struct AddAccountView: View { .navigationTitle("New Account") .navigationBarTitleDisplayMode(.inline) .toolbar { - ToolbarItem(placement: .navigationBarLeading) { - Button("Cancel") { - dismiss() - } - } - ToolbarItem(placement: .navigationBarTrailing) { Button("Add") { let account = AccountModel( name: name, balance: balance, type: type, - icon: type.icon, // Используем иконку из типа аккаунта + icon: type.icon, isDefault: isDefault, currency: currency ) @@ -73,18 +54,6 @@ struct AddAccountView: View { .disabled(name.isEmpty) } } - .sheet(isPresented: $showingTypeSelector) { - NavigationView { - SelectAccountTypeView { selectedType in - type = selectedType - icon = selectedType.icon // Обновляем иконку при выборе типа - } - } - } - .onAppear { - // Используем текущую валюту из настроек - currency = settings.selectedCurrency - } } } diff --git a/Coinly/Features/Accounts/EditAccountView.swift b/Coinly/Features/Accounts/EditAccountView.swift index 023b164..696c0cf 100644 --- a/Coinly/Features/Accounts/EditAccountView.swift +++ b/Coinly/Features/Accounts/EditAccountView.swift @@ -2,6 +2,7 @@ import SwiftUI struct EditAccountView: View { @EnvironmentObject private var accountsStore: AccountsStore + @EnvironmentObject private var settings: AppSettings @Environment(\.dismiss) private var dismiss let account: AccountModel @@ -9,7 +10,6 @@ struct EditAccountView: View { @State private var type: AccountType @State private var balance: Double @State private var currency: AppSettings.Currency - @State private var icon: String @State private var isDefault: Bool init(account: AccountModel) { @@ -18,7 +18,6 @@ struct EditAccountView: View { _type = State(initialValue: account.type) _balance = State(initialValue: account.balance) _currency = State(initialValue: account.currency) - _icon = State(initialValue: account.icon) _isDefault = State(initialValue: account.isDefault) } @@ -57,6 +56,7 @@ struct EditAccountView: View { .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarTrailing) { + // В методе Save кнопки: Button("Save") { let updatedAccount = AccountModel( id: account.id, @@ -67,7 +67,11 @@ struct EditAccountView: View { isDefault: isDefault, currency: currency ) - accountsStore.updateAccount(updatedAccount) + if isDefault { + accountsStore.setDefaultAccount(updatedAccount) + } else { + accountsStore.updateAccount(updatedAccount) + } dismiss() } .disabled(name.isEmpty) @@ -80,5 +84,6 @@ struct EditAccountView: View { NavigationView { EditAccountView(account: AccountModel.previewData) .environmentObject(AccountsStore.shared) + .environmentObject(AppSettings.shared) } } diff --git a/Coinly/Features/Models/AccountModel.swift b/Coinly/Features/Models/AccountModel.swift index 85be0f6..86709ab 100644 --- a/Coinly/Features/Models/AccountModel.swift +++ b/Coinly/Features/Models/AccountModel.swift @@ -39,6 +39,18 @@ struct AccountModel: Identifiable, Hashable { ) } + func with(isDefault: Bool) -> AccountModel { + AccountModel( + id: self.id, + name: self.name, + balance: self.balance, + type: self.type, + icon: self.icon, + isDefault: isDefault, + currency: self.currency + ) + } + static func == (lhs: AccountModel, rhs: AccountModel) -> Bool { lhs.id == rhs.id } diff --git a/Coinly/Features/Models/AccountsStore.swift b/Coinly/Features/Models/AccountsStore.swift index 399f10a..7c0b47e 100644 --- a/Coinly/Features/Models/AccountsStore.swift +++ b/Coinly/Features/Models/AccountsStore.swift @@ -4,55 +4,87 @@ class AccountsStore: ObservableObject { static let shared = AccountsStore() @Published private(set) var accounts: [AccountModel] = [] + @Published private(set) var defaultAccountId: String? var totalBalance: Double { accounts.reduce(0) { $0 + $1.balance } } + var defaultAccount: AccountModel? { + accounts.first { $0.id == defaultAccountId } + } + private init() { #if DEBUG self.accounts = AccountModel.previewItems + if let firstAccount = accounts.first { + self.defaultAccountId = firstAccount.id + } #endif } func addAccount(_ account: AccountModel) { if account.isDefault { - accounts = accounts.map { acc in - AccountModel( - id: acc.id, - name: acc.name, - balance: acc.balance, - type: acc.type, - icon: acc.icon, - isDefault: false, - currency: acc.currency - ) + defaultAccountId = account.id + // Remove default flag from other accounts + accounts = accounts.map { existingAccount in + existingAccount.with(isDefault: false) } + } else if accounts.isEmpty { + // Make first account default + defaultAccountId = account.id + let newAccount = account.with(isDefault: true) + accounts.append(newAccount) + return } accounts.append(account) + objectWillChange.send() } func updateAccount(_ account: AccountModel) { - if let index = accounts.firstIndex(where: { $0.id == account.id }) { - if account.isDefault { - accounts = accounts.map { acc in - AccountModel( - id: acc.id, - name: acc.name, - balance: acc.balance, - type: acc.type, - icon: acc.icon, - isDefault: false, - currency: acc.currency - ) + guard let index = accounts.firstIndex(where: { $0.id == account.id }) else { return } + + if account.isDefault { + defaultAccountId = account.id + // Remove default flag from other accounts + accounts = accounts.map { existingAccount in + if existingAccount.id == account.id { + return account + } + return existingAccount.with(isDefault: false) + } + } else { + // If this was the default account, make sure we have a new default + if accounts[index].isDefault { + if let firstOtherAccount = accounts.first(where: { $0.id != account.id }) { + defaultAccountId = firstOtherAccount.id + updateAccount(firstOtherAccount.with(isDefault: true)) } } accounts[index] = account } + objectWillChange.send() } func deleteAccount(_ account: AccountModel) { accounts.removeAll { $0.id == account.id } + + // If we deleted the default account, make another one default + if account.isDefault { + if let firstAccount = accounts.first { + defaultAccountId = firstAccount.id + updateAccount(firstAccount.with(isDefault: true)) + } else { + defaultAccountId = nil + } + } + + objectWillChange.send() + } + + func setDefaultAccount(_ account: AccountModel) { + defaultAccountId = account.id + updateAccount(account.with(isDefault: true)) } func getAccount(withId id: String) -> AccountModel? {