123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221 |
- //
- // BudgetView.swift
- // ydnab
- //
- // Created by Andrea Franceschini on 23/09/2020.
- //
- import SwiftUI
- import Combine
- import CoreData
- /// Shows the budget values for the selected budget, month, and year.
- struct BudgetView: View {
- @State var budget: BudgetInfo
- @Binding var currentBudgetId: UUID?
- @State private var month: Int = 0
- @State private var monthName: String
- @State private var year: Int = Calendar(identifier: .gregorian).component(.year, from: Date())
- @State private var showMonthPicker: Bool = false
- @State private var showNotImplemented: Bool = false
- @State private var showQuickBudget: Bool = false
- @State private var showEditCategoryEntry: Bool = false
- @State private var editingCategoryEntry: BudgetCategoryEntry? = nil
- @AppStorage("lastLoadedBudgetId") private var lastLoadedBudgetId = ""
- @Environment(\.managedObjectContext) var managedObjectContext
- init(budget: BudgetInfo, currentBudgetId: Binding<UUID?>) {
- _budget = .init(initialValue: budget)
- _currentBudgetId = currentBudgetId
- var cal = Calendar(identifier: .gregorian)
- cal.locale = Locale(identifier: self._budget.wrappedValue.localeIdentifier)
- let now = Date()
- _month = .init(initialValue: cal.component(.month, from: now))
- _monthName = .init(initialValue: cal.monthSymbols[self._month.wrappedValue])
- _year = .init(initialValue: cal.component(.year, from: now))
- }
- func formatAmount(_ amount: NSNumber) -> String? {
- let f = NumberFormatter()
- f.locale = Locale(identifier: budget.localeIdentifier)
- f.numberStyle = .currency
- return f.string(from: amount)
- }
- // private func findBudgetCategoryEntry(withId: UUID) -> BudgetCategoryEntry? {
- // return budgetEntries.first(where: { $0.budgetCategoryId == withId })
- // }
- private func zeroBudgetEntries() -> Void {
- // for section in budget.sections {
- // for category in section.categories {
- // if let entry = findBudgetCategoryEntry(withId: category.id) {
- // entry.amount = 0
- // } else {
- // let newEntry = BudgetCategoryEntry(context: managedObjectContext)
- // newEntry.budgetId = budget.id
- // newEntry.budgetCategoryId = category.id
- // newEntry.month = Int64(month)
- // newEntry.year = Int64(year)
- // newEntry.amount = 0
- // }
- //
- // do {
- // try managedObjectContext.save()
- // } catch {
- // print(error)
- // }
- // }
- // }
- }
- @State private var refreshView: Bool = false
- var body: some View {
- DynamicFetchView(predicate: NSPredicate(format: "budgetId == %@ AND month == %ld AND year == %ld", budget.id as CVarArg, month, year),
- sortDescriptors: []) { (budgetEntries: FetchedResults<BudgetCategoryEntry>) in
- VStack {
- HStack {
- Button(action: { withAnimation() { showMonthPicker.toggle() } } ) {
- Text("\(monthName) \(String(year))")
- Image(systemName: showMonthPicker ? "chevron.up" : "chevron.down")
- }
- Spacer()
- Button(action: { showQuickBudget.toggle() }) { // TODO: Implement this
- Image(systemName: "bolt.fill")
- }
- Button(action: { showNotImplemented.toggle() }) { // TODO: Implement this
- Image(systemName: "gearshape.fill")
- }.padding(.leading, 11)
- }
- .padding(11)
- if showMonthPicker {
- YearAndMonthPicker(month: $month,
- monthName: $monthName,
- year: $year,
- locale: Locale(identifier: budget.localeIdentifier)
- )
- }
- BudgetViewSummary(budget: $budget, month: $month, monthName: $monthName, year: $year)
- .padding([.leading, .trailing], 16)
- List {
- ForEach(budget.sections) { section in
- if !section.hidden {
- Section(header: BudgetSectionCell(section: section,
- amountText: "", //formatAmount(0) ?? "--",
- color: 0 < 0 ? Color("negativeAmount") : Color("positiveAmount"))
- ) {
- ForEach(section.categories) { category in
- if !category.hidden {
- BudgetCategoryCell(category: category,
- amountText: String(format: "= %@ =", budgetEntries.first(where: { $0.budgetCategoryId == category.id })?.amount ?? -69 ),
- color: 0 < 0 ? Color("negativeAmount") : Color("positiveAmount")
- )
- }
- }
- }
- }
- }
- }
- .listStyle(PlainListStyle())
- // .onChange(of: month) { m in
- // let entries = budgetEntries
- // print(entries.map { $0.amount })
- // }
- .navigationBarTitle(budget.name, displayMode: .inline)
- .toolbar {
- EditButton()
- }
- .onAppear() {
- currentBudgetId = budget.id
- lastLoadedBudgetId = budget.id.uuidString
- }
- .actionSheet(isPresented: $showQuickBudget) {
- ActionSheet(
- title: Text("Quick budget"),
- message: Text("How would you like to set up your budget for \(monthName) \(String(year))?"),
- buttons: [
- .cancel { print(self.showQuickBudget) },
- .default(Text("All categories to zero"), action: zeroBudgetEntries),
- .default(Text("Values used the previous month"), action: { print(self) })
- ]
- )
- }
- .popover(isPresented: $showEditCategoryEntry) {
- // BudgetCategoryEntryEditor(amount: $editingCategoryEntry.wrappedValue?.amount)
- }
- .alert(isPresented: $showNotImplemented) {
- Alert(title: Text("Not Implemented!"))
- }
- }
- }
- }
- }
- struct BudgetCategoryEntryEditor: View {
- @Binding var amount: Decimal?
- @Binding var locale: Locale
- @State private var amountText = ""
- var body: some View {
- VStack {
- TextField("Amount", text: $amountText)
- .keyboardType(.numbersAndPunctuation)
- .onReceive(Just(amountText)) { newValue in
- let filtered = newValue.filter { "0123456789.,".contains($0) }
- if filtered != newValue {
- amount = Decimal(string: filtered, locale: locale)
- }
- }
- }
- .onAppear {
- amountText = "\(amount ?? -69)"
- }
- }
- }
- struct BudgetView_Previews: PreviewProvider {
- static let budget: [BudgetSection] = [
- BudgetSection(name: "Everyday Expenses", categories: [
- BudgetCategory(name: "Groceries"),
- BudgetCategory(name: "Eating out"),
- BudgetCategory(name: "Medical"),
- BudgetCategory(name: "Clothing"),
- BudgetCategory(name: "Household goods")
- ]),
- BudgetSection(name: "Travel", categories: [
- BudgetCategory(name: "Transport"),
- BudgetCategory(name: "Fuel"),
- BudgetCategory(name: "Accommodation")
- ]),
- BudgetSection(name: "Rainy Days", categories: [
- BudgetCategory(name: "Emergencies"),
- BudgetCategory(name: "Car insurance")
- ]),
- BudgetSection(name: "Monthly Expenses", categories: [
- BudgetCategory(name: "Rent"),
- BudgetCategory(name: "Mobile")
- ]),
- BudgetSection(name: "Savings Goals", categories: [
- BudgetCategory(name: "Savings"),
- BudgetCategory(name: "Holidays")
- ])
- ]
- @State static var currentBudgetId: UUID?
- static var previews: some View {
- NavigationView {
- BudgetView(budget: BudgetInfo(name: "Default Budget", sections: budget),
- currentBudgetId: $currentBudgetId)
- }
- .preferredColorScheme(.light)
- }
- }
|