Fix AccountsListView naming and ContentView navigation

This commit is contained in:
“SamoilenkoVadym” 2025-03-03 09:53:10 +00:00
parent 5861b34737
commit 739dd17b05
8 changed files with 196 additions and 152 deletions

View file

@ -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)
}

View file

@ -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()
}

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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
}
}
}

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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? {