Add sidebar layout for macOS and iPad

This commit is contained in:
William Brawner 2021-12-07 20:21:46 -07:00
parent 12a20c30b9
commit 733f1cc764
7 changed files with 173 additions and 72 deletions

View file

@ -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 = "<group>"; };
8043EB83271F26ED00498E73 /* CategoryDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryDetailsView.swift; sourceTree = "<group>"; };
806C784F272B700B00FA1375 /* TwigsApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwigsApp.swift; sourceTree = "<group>"; };
80820144275FFD380040996E /* SidebarBudgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarBudgetView.swift; sourceTree = "<group>"; };
809B942227221EC800B1DAE2 /* CategoryFormSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryFormSheet.swift; sourceTree = "<group>"; };
809B94242722597800B1DAE2 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -191,10 +193,10 @@
28AC94E1233C373900BFB70A = {
isa = PBXGroup;
children = (
28AC94EB233C373900BFB70A /* Products */,
28AC94EC233C373900BFB70A /* Twigs */,
28AC9503233C373A00BFB70A /* TwigsTests */,
28AC950E233C373A00BFB70A /* TwigsUITests */,
28AC94EB233C373900BFB70A /* Products */,
);
sourceTree = "<group>";
};
@ -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 */,

View file

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

View file

@ -35,7 +35,7 @@
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<true/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>

View file

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

View file

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

View file

@ -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<String, [Transaction]>) -> 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) {

View file

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