From 04ecb3226ac5f9d5154346f4836ae97b94615a46 Mon Sep 17 00:00:00 2001 From: William Brawner Date: Sat, 4 Mar 2023 21:56:15 -0700 Subject: [PATCH] Update SwiftUI navigation --- Twigs.xcodeproj/project.pbxproj | 4 +- Twigs/Budget/BudgetDetailsView.swift | 32 ++-- Twigs/Budget/BudgetListsView.swift | 2 +- Twigs/Category/CategoryDetailsView.swift | 25 +-- Twigs/Category/CategoryListView.swift | 45 +++--- Twigs/DataStore.swift | 17 ++ .../RecurringTransactionsListView.swift | 50 +++--- Twigs/SidebarBudgetView.swift | 81 ++++++---- Twigs/TabbedBudgetView.swift | 148 +++++++++++------- Twigs/Transaction/TransactionListView.swift | 50 +++--- 10 files changed, 266 insertions(+), 188 deletions(-) diff --git a/Twigs.xcodeproj/project.pbxproj b/Twigs.xcodeproj/project.pbxproj index 65f2fcd..b8b9d76 100644 --- a/Twigs.xcodeproj/project.pbxproj +++ b/Twigs.xcodeproj/project.pbxproj @@ -834,7 +834,7 @@ CODE_SIGN_ENTITLEMENTS = Twigs/Twigs.entitlements; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 22; + CURRENT_PROJECT_VERSION = 23; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; DEVELOPMENT_ASSET_PATHS = "\"Twigs/Preview Content\""; @@ -869,7 +869,7 @@ CODE_SIGN_ENTITLEMENTS = Twigs/Twigs.entitlements; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 22; + CURRENT_PROJECT_VERSION = 23; DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; DEVELOPMENT_ASSET_PATHS = "\"Twigs/Preview Content\""; DEVELOPMENT_TEAM = 9Z6DE6KNJ9; diff --git a/Twigs/Budget/BudgetDetailsView.swift b/Twigs/Budget/BudgetDetailsView.swift index ed06bf5..07ba203 100644 --- a/Twigs/Budget/BudgetDetailsView.swift +++ b/Twigs/Budget/BudgetDetailsView.swift @@ -74,9 +74,16 @@ struct BudgetDetailsView: View { await dataStore.loadOverview(showLoader: false) } #endif - .navigationBarItems(trailing: Button(action: { - dataStore.editBudget() - }, label: { Text("edit") })) + .toolbar { + ToolbarItem(placement: .navigationBarTrailing, content: { + Button( + action: { + dataStore.editBudget() + }, + label: { Text("edit") } + ) + }) + } .sheet( isPresented: $dataStore.editingBudget, onDismiss: { Task { @@ -85,12 +92,19 @@ struct BudgetDetailsView: View { content: { NavigationView { BudgetFormView(budget) - .navigationBarItems(leading: Button(action: { Task { - await dataStore.cancelEditBudget() - }}, label: { - Text("cancel") - }) - .navigationTitle("edit_budget")) + .toolbar { + ToolbarItem(placement: .navigationBarLeading, content: { + Button( + action: { + Task { + await dataStore.cancelEditBudget() + } + }, + label: { Text("cancel") } + ) + }) + } + .navigationTitle("edit_budget") } } ) diff --git a/Twigs/Budget/BudgetListsView.swift b/Twigs/Budget/BudgetListsView.swift index 6a2a262..b697c6f 100644 --- a/Twigs/Budget/BudgetListsView.swift +++ b/Twigs/Budget/BudgetListsView.swift @@ -20,7 +20,7 @@ struct BudgetListsView: View { action: { await self.dataStore.getBudgets(count: nil, page: nil) }, errorTextLocalizedStringKey: "budgets_load_failure" ) { (budgets: [Budget]) in - ForEach(budgets) { budget in + List(budgets, selection: $dataStore.selectedBudget) { budget in BudgetListItemView(budget) } } diff --git a/Twigs/Category/CategoryDetailsView.swift b/Twigs/Category/CategoryDetailsView.swift index 7abcc0c..3ef6af4 100644 --- a/Twigs/Category/CategoryDetailsView.swift +++ b/Twigs/Category/CategoryDetailsView.swift @@ -11,7 +11,7 @@ import TwigsCore struct CategoryDetailsView: View { @EnvironmentObject var dataStore: DataStore - @EnvironmentObject var categoryDataStore: CategoryDataStore + @ObservedObject var categoryDataStore: CategoryDataStore @EnvironmentObject var apiService: TwigsApiService let budget: Budget @State var sum: Int? = 0 @@ -47,13 +47,17 @@ struct CategoryDetailsView: View { }.task { await categoryDataStore.sum(categoryId: category.id) } - .navigationBarItems(trailing: Button(action: { - Task { - await dataStore.edit(category) - } - }) { - Text("edit") - }) + .toolbar { + ToolbarItem(placement: .navigationBarTrailing, content: { + Button(action: { + Task { + await dataStore.edit(category) + } + }) { + Text("edit") + } + }) + } .sheet(isPresented: self.$dataStore.editingCategory, onDismiss: { self.dataStore.cancelEditCategory() }, content: { @@ -66,8 +70,9 @@ struct CategoryDetailsView: View { } } - init (_ budget: Budget) { + init (_ budget: Budget, categoryDataStore: CategoryDataStore) { self.budget = budget + self.categoryDataStore = categoryDataStore } } @@ -85,7 +90,7 @@ struct LabeledCounter: View { #if DEBUG struct CategoryDetailsView_Previews: PreviewProvider { static var previews: some View { - CategoryDetailsView(MockBudgetRepository.budget) + CategoryDetailsView(MockBudgetRepository.budget, categoryDataStore: CategoryDataStore(TwigsInMemoryCacheService())) .environmentObject(TwigsInMemoryCacheService()) } } diff --git a/Twigs/Category/CategoryListView.swift b/Twigs/Category/CategoryListView.swift index ebcedc9..880b133 100644 --- a/Twigs/Category/CategoryListView.swift +++ b/Twigs/Category/CategoryListView.swift @@ -25,7 +25,7 @@ struct CategoryListView: View { @ViewBuilder var body: some View { - List { + List(selection: $dataStore.selectedCategory) { InlineLoadingView( data: $dataStore.categories, action: { await self.dataStore.getCategories(budgetId: budget.id, expense: nil, archived: nil, count: nil, page: nil) }, @@ -101,32 +101,25 @@ struct CategoryListItemView: View { } var body: some View { - NavigationLink( - tag: category, - selection: $dataStore.selectedCategory, - destination: { - CategoryDetailsView(self.budget) - .environmentObject(categoryDataStore) - .navigationBarTitle(dataStore.selectedCategory?.title ?? "") - }, - label: { - VStack(alignment: .leading) { - HStack { - Text(verbatim: category.title) - Spacer() - remaining - } - if category.description?.isEmpty == false { - Text(verbatim: category.description!) - .font(.subheadline) - .foregroundColor(.secondary) - .lineLimit(1) - } - progressView - }.task { - await categoryDataStore.sum(categoryId: category.id) + NavigationLink(value: category, label: { + VStack(alignment: .leading) { + HStack { + Text(verbatim: category.title) + Spacer() + remaining } - }) + if category.description?.isEmpty == false { + Text(verbatim: category.description!) + .font(.subheadline) + .foregroundColor(.secondary) + .lineLimit(1) + } + progressView + }.task { + await categoryDataStore.sum(categoryId: category.id) + } + }) + .environmentObject(categoryDataStore) } var progressView: ProgressView { diff --git a/Twigs/DataStore.swift b/Twigs/DataStore.swift index 7e2cef4..2c32391 100644 --- a/Twigs/DataStore.swift +++ b/Twigs/DataStore.swift @@ -46,6 +46,23 @@ class DataStore : ObservableObject { } } + var selectedBudget: Budget? { + get { + if case let .success(budget) = self.budget { + return budget + } + + return nil + } + set { + if let budget = newValue { + self.budget = .success(budget) + } else { + self.budget = .empty + } + } + } + var budgetId: String? { get { if case let .success(budget) = self.budget { diff --git a/Twigs/Recurring Transactions/RecurringTransactionsListView.swift b/Twigs/Recurring Transactions/RecurringTransactionsListView.swift index 8fe7258..9d11477 100644 --- a/Twigs/Recurring Transactions/RecurringTransactionsListView.swift +++ b/Twigs/Recurring Transactions/RecurringTransactionsListView.swift @@ -19,7 +19,7 @@ struct RecurringTransactionsListView: View { action: { await self.dataStore.getRecurringTransactions() }, errorTextLocalizedStringKey: "Failed to load recurring transactions" ) { (transactions: OrderedDictionary) in - List { + List(selection: $dataStore.selectedRecurringTransaction) { ForEach(transactions.keys, id: \.self) { (key: String) in Group { if !transactions[key]!.isEmpty { @@ -82,35 +82,31 @@ struct RecurringTransactionsListItemView: View { var body: some View { NavigationLink( - tag: transaction, - selection: $dataStore.selectedRecurringTransaction, - destination: { - RecurringTransactionDetailsView() - .navigationBarTitle("details", displayMode: .inline) - } - ) { - HStack { - VStack(alignment: .leading) { - Text(verbatim: transaction.title) - .lineLimit(1) - .font(.headline) - if let description = transaction.description?.trimmingCharacters(in: CharacterSet([" "])), !description.isEmpty { - Text(verbatim: description) + value: transaction, + label: { + HStack { + VStack(alignment: .leading) { + Text(verbatim: transaction.title) .lineLimit(1) - .font(.subheadline) - .foregroundColor(.secondary) + .font(.headline) + if let description = transaction.description?.trimmingCharacters(in: CharacterSet([" "])), !description.isEmpty { + Text(verbatim: description) + .lineLimit(1) + .font(.subheadline) + .foregroundColor(.secondary) + .multilineTextAlignment(.trailing) + } + } + Spacer() + VStack(alignment: .trailing) { + Text(verbatim: transaction.amount.toCurrencyString()) + .foregroundColor(transaction.expense ? .red : .green) .multilineTextAlignment(.trailing) } - } - Spacer() - VStack(alignment: .trailing) { - Text(verbatim: transaction.amount.toCurrencyString()) - .foregroundColor(transaction.expense ? .red : .green) - .multilineTextAlignment(.trailing) - } - .padding(.leading) - }.padding(5.0) - } + .padding(.leading) + }.padding(5.0) + } + ) } } diff --git a/Twigs/SidebarBudgetView.swift b/Twigs/SidebarBudgetView.swift index 77dd7e6..05e2a80 100644 --- a/Twigs/SidebarBudgetView.swift +++ b/Twigs/SidebarBudgetView.swift @@ -18,43 +18,66 @@ struct SidebarBudgetView: View { @ViewBuilder var mainView: some View { if case let .success(budget) = self.dataStore.budget { - NavigationView { - List { - NavigationLink( - tag: 0, - selection: $tabSelection, - destination: { BudgetDetailsView(budget: budget).navigationBarTitle("overview") - }, - label: { Label("overview", systemImage: "chart.line.uptrend.xyaxis") } - ) + NavigationSplitView(sidebar: { + VStack { + List(selection: $tabSelection) { + NavigationLink( + value: 0, + label: { Label("overview", systemImage: "chart.line.uptrend.xyaxis") } + ) .keyboardShortcut("1") - NavigationLink( - tag: 1, - selection: $tabSelection, - destination: { TransactionListView().navigationBarTitle("transactions") }, - label: { Label("transactions", systemImage: "dollarsign.circle") }) + NavigationLink( + value: 1, + label: { Label("transactions", systemImage: "dollarsign.circle") } + ) .keyboardShortcut("2") - NavigationLink( - tag: 2, - selection: $tabSelection, - destination: { CategoryListView(budget).navigationBarTitle("categories") }, - label: { Label("categories", systemImage: "chart.pie") }) + NavigationLink( + value: 2, + label: { Label("categories", systemImage: "chart.pie") } + ) .keyboardShortcut("3") - NavigationLink( - tag: 3, - selection: $tabSelection, - destination: { RecurringTransactionsListView().navigationBarTitle("recurring_transactions") }, - label: { Label("recurring_transactions", systemImage: "arrow.triangle.2.circlepath") }) + NavigationLink( + value: 3, + label: { Label("recurring_transactions", systemImage: "arrow.triangle.2.circlepath") } + ) .keyboardShortcut("4") - Divider() + } BudgetListsView() } .navigationTitle(budget.name) - if self.tabSelection ?? 0 > 0 { - EmptyView() - EmptyView() + }, content: { + if tabSelection == 0, let budget = dataStore.selectedBudget { + BudgetDetailsView(budget: budget) + .navigationTitle("budgets") + } else if tabSelection == 1 { + TransactionListView() + .navigationTitle("transactions") + } else if tabSelection == 2, let budget = dataStore.selectedBudget { + CategoryListView(budget) + .navigationTitle("categories") + } else if tabSelection == 3 { + RecurringTransactionsListView() + .navigationTitle("recurring_transactions") + } else { + ActivityIndicator(isAnimating: .constant(true), style: .large) } - } + }, detail: { + if let _ = dataStore.selectedTransaction { + TransactionDetailsView() + .navigationTitle("details") + .onDisappear { + dataStore.selectedTransaction = nil + } + } else if let _ = dataStore.selectedCategory { + if let budget = dataStore.selectedBudget { + CategoryDetailsView(budget, categoryDataStore: CategoryDataStore(dataStore.apiService)) + .navigationTitle(dataStore.selectedCategory?.title ?? "") + } + } else if let _ = dataStore.selectedRecurringTransaction { + RecurringTransactionDetailsView() + .navigationTitle("details") + } + }) } else { ActivityIndicator(isAnimating: .constant(true), style: .large) } diff --git a/Twigs/TabbedBudgetView.swift b/Twigs/TabbedBudgetView.swift index 52393e5..1d461c2 100644 --- a/Twigs/TabbedBudgetView.swift +++ b/Twigs/TabbedBudgetView.swift @@ -21,60 +21,100 @@ struct TabbedBudgetView: View { TabView(selection: $tabSelection) { NavigationView { BudgetDetailsView(budget: budget) - .navigationBarTitle("overview") - .navigationBarItems(leading: HStack { - Button("budgets", action: { - self.dataStore.showBudgetSelection = true - }).padding() - }) + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + HStack { + Button("budgets", action: { + self.dataStore.showBudgetSelection = true + }).padding() + } + } + } + .navigationTitle(budget.name) } - .tabItem { - Image(systemName: "chart.line.uptrend.xyaxis.circle.fill") - Text("overview") - } - .tag(0) - .keyboardShortcut("1") - NavigationView { - TransactionListView() - .navigationBarTitle("transactions") - } - .tabItem { - Image(systemName: "dollarsign.circle.fill") - Text("transactions") - } - .tag(1) - .keyboardShortcut("2") - NavigationView { - CategoryListView(budget) - .navigationBarTitle("categories") - } - .tabItem { - Image(systemName: "chart.pie.fill") - Text("categories") - } - .tag(2) - .keyboardShortcut("3") - NavigationView { - RecurringTransactionsListView() - .navigationBarTitle("recurring_transactions") - } - .tabItem { - Image(systemName: "arrow.triangle.2.circlepath.circle.fill") - Text("recurring") - } - .tag(3) - .keyboardShortcut("4") - NavigationView { - ProfileView() - .navigationBarTitle("profile") - } - .tabItem { - Image(systemName: "person.circle.fill") - Text("profile") - } - .tag(4) - .keyboardShortcut("5") + .tabItem { + Image(systemName: "chart.line.uptrend.xyaxis.circle.fill") + Text("overview") + } + .tag(0) + .keyboardShortcut("1") + NavigationSplitView( + sidebar: { + TransactionListView() + .navigationTitle(budget.name) + }, + detail: { + if let _ = dataStore.selectedTransaction { + TransactionDetailsView() + .navigationTitle("details") + .onDisappear { + dataStore.selectedTransaction = nil + } + } else { + ActivityIndicator(isAnimating: .constant(true), style: .large) + } + }) + .tabItem { + Image(systemName: "dollarsign.circle.fill") + Text("transactions") + } + .tag(1) + .keyboardShortcut("2") + NavigationSplitView( + sidebar: { + CategoryListView(budget) + .navigationTitle(budget.name) + }, + content: { + if let _ = dataStore.selectedCategory { + if let budget = dataStore.selectedBudget { + CategoryDetailsView(budget, categoryDataStore: CategoryDataStore(dataStore.apiService)) + .navigationTitle(dataStore.selectedCategory?.title ?? "") + } + } + }, + detail: { + if let _ = dataStore.selectedTransaction { + TransactionDetailsView() + .navigationTitle("details") + } else { + ActivityIndicator(isAnimating: .constant(true), style: .large) + } + }) + .tabItem { + Image(systemName: "chart.pie.fill") + Text("categories") + } + .tag(2) + .keyboardShortcut("3") + NavigationSplitView( + sidebar: { + RecurringTransactionsListView() + .navigationTitle(budget.name) + }, + detail: { + if let _ = dataStore.selectedRecurringTransaction { + RecurringTransactionDetailsView() + .navigationTitle("details") + } else { + ActivityIndicator(isAnimating: .constant(true), style: .large) + } + }) + .tabItem { + Image(systemName: "arrow.triangle.2.circlepath.circle.fill") + Text("recurring") + } + .tag(3) + .keyboardShortcut("4") + ProfileView() + .tabItem { + Image(systemName: "person.circle.fill") + Text("profile") + } + .tag(4) + .keyboardShortcut("5") } + .navigationTitle(budget.name) default: ActivityIndicator(isAnimating: .constant(true), style: .large) } @@ -91,9 +131,7 @@ struct TabbedBudgetView: View { content: { NavigationView { VStack { - List { - BudgetListsView().environmentObject(dataStore) - } + BudgetListsView().environmentObject(dataStore) .navigationTitle("budgets") .navigationBarItems(trailing: Button(action: {dataStore.newBudget()}, label: { Image(systemName: "plus") diff --git a/Twigs/Transaction/TransactionListView.swift b/Twigs/Transaction/TransactionListView.swift index 046388b..06a401b 100644 --- a/Twigs/Transaction/TransactionListView.swift +++ b/Twigs/Transaction/TransactionListView.swift @@ -91,7 +91,7 @@ struct TransactionListView: View where Content: View { action: { await dataStore.getTransactions() }, errorTextLocalizedStringKey: "Failed to load transactions" ) { transactions in - List { + List(selection: $dataStore.selectedTransaction) { TransactionList(transactions) } .searchable(text: $search) @@ -134,37 +134,29 @@ struct TransactionListItemView: View { var transaction: TwigsCore.Transaction var body: some View { - NavigationLink( - tag: self.transaction, - selection: self.$dataStore.selectedTransaction, - destination: { - TransactionDetailsView() - .navigationBarTitle("details", displayMode: .inline) - }, - label: { - HStack { - VStack(alignment: .leading) { - Text(verbatim: transaction.title) + NavigationLink(value: self.transaction, label: { + HStack { + VStack(alignment: .leading) { + Text(verbatim: transaction.title) + .lineLimit(1) + .font(.headline) + if let description = transaction.description?.trimmingCharacters(in: CharacterSet([" "])), !description.isEmpty { + Text(verbatim: description) .lineLimit(1) - .font(.headline) - if let description = transaction.description?.trimmingCharacters(in: CharacterSet([" "])), !description.isEmpty { - Text(verbatim: description) - .lineLimit(1) - .font(.subheadline) - .foregroundColor(.secondary) - .multilineTextAlignment(.trailing) - } - } - Spacer() - VStack(alignment: .trailing) { - Text(verbatim: transaction.amount.toCurrencyString()) - .foregroundColor(transaction.expense ? .red : .green) + .font(.subheadline) + .foregroundColor(.secondary) .multilineTextAlignment(.trailing) } - .padding(.leading) - }.padding(5.0) - } - ) + } + Spacer() + VStack(alignment: .trailing) { + Text(verbatim: transaction.amount.toCurrencyString()) + .foregroundColor(transaction.expense ? .red : .green) + .multilineTextAlignment(.trailing) + } + .padding(.leading) + }.padding(5.0) + }) } init (_ transaction: TwigsCore.Transaction) {