diff --git a/Twigs.xcodeproj/project.pbxproj b/Twigs.xcodeproj/project.pbxproj index 18fd9c8..7b467d3 100644 --- a/Twigs.xcodeproj/project.pbxproj +++ b/Twigs.xcodeproj/project.pbxproj @@ -47,6 +47,7 @@ 543ECE42233E82A40018A9D9 /* AuthenticationDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 543ECE41233E82A40018A9D9 /* AuthenticationDataStore.swift */; }; 8043EB84271F26ED00498E73 /* CategoryDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8043EB83271F26ED00498E73 /* CategoryDetailsView.swift */; }; 806C7850272B700B00FA1375 /* TwigsApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 806C784F272B700B00FA1375 /* TwigsApp.swift */; }; + 80820145275FFD380040996E /* SidebarBudgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80820144275FFD380040996E /* SidebarBudgetView.swift */; }; 8094A9C327567CAC006C6C62 /* Collections in Frameworks */ = {isa = PBXBuildFile; productRef = 8094A9C227567CAC006C6C62 /* Collections */; }; 809B942327221EC800B1DAE2 /* CategoryFormSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 809B942227221EC800B1DAE2 /* CategoryFormSheet.swift */; }; /* End PBXBuildFile section */ @@ -116,6 +117,7 @@ 543ECE41233E82A40018A9D9 /* AuthenticationDataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationDataStore.swift; sourceTree = ""; }; 8043EB83271F26ED00498E73 /* CategoryDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryDetailsView.swift; sourceTree = ""; }; 806C784F272B700B00FA1375 /* TwigsApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwigsApp.swift; sourceTree = ""; }; + 80820144275FFD380040996E /* SidebarBudgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarBudgetView.swift; sourceTree = ""; }; 809B942227221EC800B1DAE2 /* CategoryFormSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryFormSheet.swift; sourceTree = ""; }; 809B94242722597800B1DAE2 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; /* End PBXFileReference section */ @@ -191,10 +193,10 @@ 28AC94E1233C373900BFB70A = { isa = PBXGroup; children = ( + 28AC94EB233C373900BFB70A /* Products */, 28AC94EC233C373900BFB70A /* Twigs */, 28AC9503233C373A00BFB70A /* TwigsTests */, 28AC950E233C373A00BFB70A /* TwigsUITests */, - 28AC94EB233C373900BFB70A /* Products */, ); sourceTree = ""; }; @@ -218,6 +220,7 @@ 28AC94F1233C373900BFB70A /* LoginView.swift */, 2888234623512DBF003D3847 /* Observable.swift */, 28B9E50D2346BCB2007C3909 /* RegistrationView.swift */, + 80820144275FFD380040996E /* SidebarBudgetView.swift */, 2841022623419A2B00EAFA29 /* TabbedBudgetView.swift */, 806C784F272B700B00FA1375 /* TwigsApp.swift */, 28AC94F3233C373A00BFB70A /* Assets.xcassets */, @@ -465,6 +468,7 @@ 28FE6AF62342E4CC00D5543E /* BudgetRepository.swift in Sources */, 28FE6B022344331B00D5543E /* TransactionDataStore.swift in Sources */, 543ECE42233E82A40018A9D9 /* AuthenticationDataStore.swift in Sources */, + 80820145275FFD380040996E /* SidebarBudgetView.swift in Sources */, 284102302342D97300EAFA29 /* BudgetListsView.swift in Sources */, 282126BD235CDE1400072D52 /* ProgressView.swift in Sources */, 806C7850272B700B00FA1375 /* TwigsApp.swift in Sources */, diff --git a/Twigs/Budget/BudgetListsView.swift b/Twigs/Budget/BudgetListsView.swift index f5d5cdd..0c6822d 100644 --- a/Twigs/Budget/BudgetListsView.swift +++ b/Twigs/Budget/BudgetListsView.swift @@ -15,25 +15,21 @@ struct BudgetListsView: View { @ViewBuilder var body: some View { - NavigationView { - switch budgetDataStore.budgets { - case .success(let budgets): - Section { - List(budgets) { budget in - BudgetListItemView(budget) - } - }.navigationBarTitle("budgets") - case .failure(.loading): - VStack { - ActivityIndicator(isAnimating: .constant(true), style: .large) - }.navigationBarTitle("budgets") - default: - // TODO: Handle each network failure type - Text("budgets_load_failure").navigationBarTitle("budgets") - Button("action_retry", action: { - self.budgetDataStore.getBudgets() - }) + switch budgetDataStore.budgets { + case .success(let budgets): + Section("budgets") { + ForEach(budgets) { budget in + BudgetListItemView(budget) + } } + case .failure(.loading): + ActivityIndicator(isAnimating: .constant(true), style: .large) + default: + // TODO: Handle each network failure type + Text("budgets_load_failure").navigationBarTitle("budgets") + Button("action_retry", action: { + self.budgetDataStore.getBudgets() + }) } } } diff --git a/Twigs/Info.plist b/Twigs/Info.plist index f1d5135..13111b6 100644 --- a/Twigs/Info.plist +++ b/Twigs/Info.plist @@ -35,7 +35,7 @@ UIApplicationSceneManifest UIApplicationSupportsMultipleScenes - + UISceneConfigurations UIWindowSceneSessionRoleApplication diff --git a/Twigs/SidebarBudgetView.swift b/Twigs/SidebarBudgetView.swift new file mode 100644 index 0000000..39360a0 --- /dev/null +++ b/Twigs/SidebarBudgetView.swift @@ -0,0 +1,82 @@ +// +// SidebarBudgetView.swift +// Twigs +// +// Created by William Brawner on 12/7/21. +// Copyright © 2021 William Brawner. All rights reserved. +// + +import SwiftUI + +struct SidebarBudgetView: View { + @EnvironmentObject var authenticationDataStore: AuthenticationDataStore + @EnvironmentObject var budgetDataStore: BudgetsDataStore + @EnvironmentObject var categoryDataStore: CategoryDataStore + let apiService: TwigsApiService + @State var isSelectingBudget = true + @State var hasSelectedBudget = false + @State var isAddingTransaction = false + @State var tabSelection: Int? = 0 + + @ViewBuilder + var mainView: some View { + if case let .success(budget) = budgetDataStore.budget { + NavigationView { + List { + NavigationLink( + tag: 0, + selection: $tabSelection, + destination: { BudgetDetailsView(budget: budget).navigationBarTitle("overview") }, + label: { Label("overview", systemImage: "chart.line.uptrend.xyaxis.circle.fill") } + ) + .keyboardShortcut("1") + NavigationLink( + tag: 1, + selection: $tabSelection, + destination: { TransactionListView(budget).navigationBarTitle("transactions") }, + label: { Label("transactions", systemImage: "dollarsign.circle.fill") }) + .keyboardShortcut("2") + NavigationLink( + tag: 2, + selection: $tabSelection, + destination: { CategoryListView(budget).navigationBarTitle("categories") }, + label: { Label("categories", systemImage: "chart.pie.fill") }) + .keyboardShortcut("3") + NavigationLink( + tag: 3, + selection: $tabSelection, + destination: { RecurringTransactionsListView(dataStore: RecurringTransactionDataStore(apiService, budgetId: budget.id)).navigationBarTitle("recurring_transactions") }, + label: { Label("recurring_transactions", systemImage: "arrow.triangle.2.circlepath.circle.fill") }) + .keyboardShortcut("4") + BudgetListsView() + } + .navigationTitle(budget.name) + } + } else { + ActivityIndicator(isAnimating: .constant(true), style: .large) + } + } + + @ViewBuilder + var body: some View { + mainView + .sheet(isPresented: $authenticationDataStore.showLogin, + onDismiss: { + self.budgetDataStore.getBudgets() + }, + content: { + LoginView() + .environmentObject(authenticationDataStore) + .onDisappear { + self.budgetDataStore.getBudgets() + } + }) + .interactiveDismissDisabled(true) + } +} + +//struct SidebarBudgetView_Previews: PreviewProvider { +// static var previews: some View { +// SidebarBudgetView() +// } +//} diff --git a/Twigs/TabbedBudgetView.swift b/Twigs/TabbedBudgetView.swift index c166691..515b9e5 100644 --- a/Twigs/TabbedBudgetView.swift +++ b/Twigs/TabbedBudgetView.swift @@ -24,6 +24,11 @@ struct TabbedBudgetView: View { NavigationView { BudgetDetailsView(budget: budget) .navigationBarTitle("overview") + .navigationBarItems(leading: HStack { + Button("budgets", action: { + self.budgetDataStore.showBudgetSelection = true + }).padding() + }) } .tabItem { Image(systemName: "chart.line.uptrend.xyaxis.circle.fill") @@ -88,8 +93,9 @@ struct TabbedBudgetView: View { } }).sheet(isPresented: $budgetDataStore.showBudgetSelection, content: { - BudgetListsView() - .environmentObject(budgetDataStore) + List { + BudgetListsView() + } }) .interactiveDismissDisabled(true) } diff --git a/Twigs/Transaction/TransactionListView.swift b/Twigs/Transaction/TransactionListView.swift index bb0d002..b345f17 100644 --- a/Twigs/Transaction/TransactionListView.swift +++ b/Twigs/Transaction/TransactionListView.swift @@ -8,6 +8,7 @@ import SwiftUI import Combine +import Collections struct TransactionListView: View { @EnvironmentObject var transactionDataStore: TransactionDataStore @@ -16,62 +17,65 @@ struct TransactionListView: View { let header: AnyView? @ViewBuilder - var body: some View { - switch transactionDataStore.transactions[requestId] { - case .success(let transactions): - Group { - if transactions.isEmpty { - Text("no_transactions") - } else { - List { - if let header = header { - Section { - header - } - } - ForEach(transactions.keys, id: \.self) { (key: String) in - Group { - Section(header: Text(key)) { - ForEach(transactions[key]!) { transaction in - TransactionListItemView(transaction) - } - } - } + private func TransactionList(_ transactions: OrderedDictionary) -> some View { + if transactions.isEmpty { + Text("no_transactions") + } else { + if let header = header { + Section { + header + } + } + ForEach(transactions.keys, id: \.self) { (key: String) in + Group { + Section(header: Text(key)) { + ForEach(transactions[key]!) { transaction in + TransactionListItemView(transaction) } } } } - .sheet(isPresented: $isAddingTransaction, content: { - AddTransactionView(showSheet: $isAddingTransaction, budgetId: self.budget.id) - .navigationBarTitle("add_transaction") - }) - .navigationBarItems( - trailing: HStack { - Button(action: { - self.isAddingTransaction = true - }) { - Image(systemName: "plus") - .padding() - } - } - ) - case nil, .failure(.loading): - VStack { - ActivityIndicator(isAnimating: .constant(true), style: .large) - }.onAppear { - if transactionDataStore.transactions[requestId] == nil || self.requestId == "" { - self.requestId = transactionDataStore.getTransactions(self.budget.id, categoryId: self.category?.id) - } - } - default: - // TODO: Handle each network failure type - Text("budgets_load_failure") - Button("action_retry", action: { - self.requestId = transactionDataStore.getTransactions(self.budget.id, categoryId: self.category?.id) - }) } } + @ViewBuilder + var body: some View { + switch transactionDataStore.transactions[requestId] { + case .success(let transactions): + List { + TransactionList(transactions) + } + .sheet(isPresented: $isAddingTransaction, content: { + AddTransactionView(showSheet: $isAddingTransaction, budgetId: self.budget.id) + .navigationBarTitle("add_transaction") + }) + .navigationBarItems( + trailing: HStack { + Button(action: { + self.isAddingTransaction = true + }) { + Image(systemName: "plus") + .padding() + } + } + ) + case nil, .failure(.loading): + ActivityIndicator(isAnimating: .constant(true), style: .large).onAppear { + if transactionDataStore.transactions[requestId] == nil || self.requestId == "" { + self.requestId = transactionDataStore.getTransactions(self.budget.id, categoryId: self.category?.id) + } + } + default: + // TODO: Handle each network failure type + List { + Text("budgets_load_failure") + Button("action_retry", action: { + self.requestId = transactionDataStore.getTransactions(self.budget.id, categoryId: self.category?.id) + }) + } + } + } + let budget: Budget let category: Category? init(_ budget: Budget, category: Category? = nil, header: AnyView? = nil) { diff --git a/Twigs/TwigsApp.swift b/Twigs/TwigsApp.swift index 6cd2aa2..3d5993a 100644 --- a/Twigs/TwigsApp.swift +++ b/Twigs/TwigsApp.swift @@ -33,9 +33,18 @@ struct TwigsApp: App { ) } + @ViewBuilder + var mainView: some View { + if UIDevice.current.userInterfaceIdiom == .mac || UIDevice.current.userInterfaceIdiom == .pad { + SidebarBudgetView(apiService: apiService) + } else { + TabbedBudgetView(apiService: apiService) + } + } + var body: some Scene { WindowGroup { - TabbedBudgetView() + mainView .environmentObject(dataStoreProvider.authenticationDataStore()) .environmentObject(dataStoreProvider.budgetsDataStore()) .environmentObject(dataStoreProvider.categoryDataStore())