Update SwiftUI navigation

This commit is contained in:
William Brawner 2023-03-04 21:56:15 -07:00
parent 6af0fda86f
commit 04ecb3226a
10 changed files with 266 additions and 188 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -19,7 +19,7 @@ struct RecurringTransactionsListView: View {
action: { await self.dataStore.getRecurringTransactions() },
errorTextLocalizedStringKey: "Failed to load recurring transactions"
) { (transactions: OrderedDictionary<String, [RecurringTransaction]>) 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)
}
)
}
}

View file

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

View file

@ -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<EmptyView>()
.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<EmptyView>()
.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")

View file

@ -91,7 +91,7 @@ struct TransactionListView<Content>: 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) {