Fix budget/category/user disappearing on transaction details

This commit is contained in:
William Brawner 2023-01-03 21:26:42 -07:00
parent f6ff3bc724
commit 9f7ef79c98
5 changed files with 53 additions and 120 deletions

View file

@ -45,7 +45,6 @@
8043704728CBF16600F229F9 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 8043704628CBF16600F229F9 /* GoogleService-Info.plist */; };
8043EB84271F26ED00498E73 /* CategoryDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8043EB83271F26ED00498E73 /* CategoryDetailsView.swift */; };
8044BA3927828E9D009A78D4 /* CategoryDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8044BA3827828E9D009A78D4 /* CategoryDataStore.swift */; };
8044BA3B2784B659009A78D4 /* TransactionDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8044BA3A2784B659009A78D4 /* TransactionDetails.swift */; };
8044BA3D2784CC0D009A78D4 /* TransactionForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8044BA3C2784CC0D009A78D4 /* TransactionForm.swift */; };
8044BA3F27853054009A78D4 /* CategoryForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8044BA3E27853054009A78D4 /* CategoryForm.swift */; };
806C7850272B700B00FA1375 /* TwigsApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 806C784F272B700B00FA1375 /* TwigsApp.swift */; };
@ -144,7 +143,6 @@
8043704628CBF16600F229F9 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "../../../Downloads/GoogleService-Info.plist"; sourceTree = "<group>"; };
8043EB83271F26ED00498E73 /* CategoryDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryDetailsView.swift; sourceTree = "<group>"; };
8044BA3827828E9D009A78D4 /* CategoryDataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryDataStore.swift; sourceTree = "<group>"; };
8044BA3A2784B659009A78D4 /* TransactionDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionDetails.swift; sourceTree = "<group>"; };
8044BA3C2784CC0D009A78D4 /* TransactionForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionForm.swift; sourceTree = "<group>"; };
8044BA3E27853054009A78D4 /* CategoryForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryForm.swift; sourceTree = "<group>"; };
806C784F272B700B00FA1375 /* TwigsApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwigsApp.swift; sourceTree = "<group>"; };
@ -346,7 +344,6 @@
28FE6B03234449DC00D5543E /* TransactionListView.swift */,
28FE6B0523444A9800D5543E /* TransactionDetailsView.swift */,
2821265F23555FD300072D52 /* TransactionFormSheet.swift */,
8044BA3A2784B659009A78D4 /* TransactionDetails.swift */,
8044BA3C2784CC0D009A78D4 /* TransactionForm.swift */,
);
path = Transaction;
@ -621,7 +618,6 @@
2857EAED233DA30B0026BC83 /* LoadingView.swift in Sources */,
282126A3235ABC1800072D52 /* TwigsInMemoryCacheService.swift in Sources */,
800DFC2C277FF47A00EDCE9B /* AsyncData.swift in Sources */,
8044BA3B2784B659009A78D4 /* TransactionDetails.swift in Sources */,
28B9E50E2346BCB2007C3909 /* RegistrationView.swift in Sources */,
284102322342E12F00EAFA29 /* CategoryListView.swift in Sources */,
8043EB84271F26ED00498E73 /* CategoryDetailsView.swift in Sources */,

View file

@ -103,6 +103,14 @@ class DataStore : ObservableObject {
showBudgetSelection = true
}
}
func getBudget(_ id: String) -> TwigsCore.Budget? {
if case let .success(budgets) = self.budgets {
return budgets.first(where: { $0.id == id })
}
return nil
}
func newBudget() {
self.budget = .editing(Budget(id: "", name: "", description: "", currencyCode: ""))
@ -252,6 +260,14 @@ class DataStore : ObservableObject {
self.categories = .error(error)
}
}
func getCategory(_ id: String) -> TwigsCore.Category? {
if case let .success(categories) = self.categories {
return categories.first(where: { $0.id == id })
}
return nil
}
func save(_ category: TwigsCore.Category) async {
self.category = .loading
@ -731,7 +747,7 @@ class DataStore : ObservableObject {
self.user = .success(user)
} catch {
self.errorReporter.reportError(error: error)
self.currentUser = .error(error)
self.user = .error(error)
}
}
}

View file

@ -1,66 +0,0 @@
//
// TransactionDetail.swift
// Twigs
//
// Created by William Brawner on 1/4/22.
// Copyright © 2022 William Brawner. All rights reserved.
//
import Foundation
import TwigsCore
@MainActor
class TransactionDetails: ObservableObject {
@Published var category: AsyncData<TwigsCore.Category> = .empty
@Published var budget: AsyncData<Budget> = .empty
@Published var user: AsyncData<User> = .empty
let apiService: TwigsApiService
init(_ apiService: TwigsApiService) {
self.apiService = apiService
}
func loadDetails(_ transaction: TwigsCore.Transaction) async {
Task {
await loadBudget(transaction.budgetId)
}
Task {
if let categoryId = transaction.categoryId {
await loadCategory(categoryId)
}
}
Task {
await loadUser(transaction.createdBy)
}
}
private func loadBudget(_ id: String) async {
self.budget = .loading
do {
let budget = try await apiService.getBudget(id)
self.budget = .success(budget)
} catch {
self.budget = .error(error)
}
}
private func loadCategory(_ id: String) async {
self.category = .loading
do {
let category = try await apiService.getCategory(id)
self.category = .success(category)
} catch {
self.category = .error(error)
}
}
private func loadUser(_ id: String) async {
self.user = .loading
do {
let user = try await apiService.getUser(id)
self.user = .success(user)
} catch {
self.user = .error(error)
}
}
}

View file

@ -13,7 +13,6 @@ import ArgumentParser
struct TransactionDetailsView: View {
@EnvironmentObject var apiService: TwigsApiService
@EnvironmentObject var dataStore: DataStore
@ObservedObject var transactionDetails: TransactionDetails
var editing: Bool {
if case .editing(_) = dataStore.transaction {
return true
@ -24,9 +23,6 @@ struct TransactionDetailsView: View {
return false
}
init(_ transactionDetails: TransactionDetails) {
self.transactionDetails = transactionDetails
}
private var currentUserId: String? {
get {
if case let .success(currentUser) = self.dataStore.currentUser {
@ -55,14 +51,13 @@ struct TransactionDetailsView: View {
if let description = transaction.description {
LabeledField(label: "notes", value: description, loading: .constant(false), showDivider: true)
}
CategoryLineItem()
BudgetLineItem()
UserLineItem()
}.padding()
.environmentObject(transactionDetails)
.task {
await transactionDetails.loadDetails(transaction)
if let categoryId = transaction.categoryId {
CategoryLineItem(id: categoryId)
}
BudgetLineItem(id: transaction.budgetId)
UserLineItem(id: transaction.createdBy)
}
.padding()
}
.navigationBarItems(trailing: Button(
action: { self.dataStore.editTransaction(transaction) }
@ -92,32 +87,31 @@ struct LabeledField: View {
@ViewBuilder
var body: some View {
if let val = value, !val.isEmpty {
VStack {
HStack {
Text(self.label)
.foregroundColor(.secondary)
Spacer()
if loading {
EmbeddedLoadingView()
} else {
Text(verbatim: val)
.multilineTextAlignment(.trailing)
}
}
if showDivider {
Divider()
VStack {
HStack {
Text(self.label)
.foregroundColor(.secondary)
Spacer()
if loading {
EmbeddedLoadingView()
} else {
Text(verbatim: self.value ?? "")
.multilineTextAlignment(.trailing)
}
}
if showDivider {
Divider()
}
}
}
}
struct CategoryLineItem: View {
@EnvironmentObject var transactionDetails: TransactionDetails
let id: String
@EnvironmentObject var dataStore: DataStore
var value: String {
// TODO: Show errors
if case let .success(category) = transactionDetails.category {
if let category = dataStore.getCategory(id) {
return category.title
} else {
return ""
@ -126,19 +120,16 @@ struct CategoryLineItem: View {
@ViewBuilder
var body: some View {
if case .empty = transactionDetails.category {
EmptyView()
} else {
LabeledField(label: "category", value: value, loading: .constant(self.value == ""), showDivider: true)
}
LabeledField(label: "category", value: value, loading: .constant(self.value == ""), showDivider: true)
}
}
struct BudgetLineItem: View {
@EnvironmentObject var transactionDetails: TransactionDetails
let id: String
@EnvironmentObject var dataStore: DataStore
var value: String {
// TODO: Show errors
if case let .success(budget) = transactionDetails.budget {
if let budget = dataStore.getBudget(id) {
return budget.name
} else {
return ""
@ -147,19 +138,16 @@ struct BudgetLineItem: View {
@ViewBuilder
var body: some View {
if case .empty = transactionDetails.budget {
EmptyView()
} else {
LabeledField(label: "budget", value: value, loading: .constant(self.value == ""), showDivider: true)
}
LabeledField(label: "budget", value: value, loading: .constant(self.value == ""), showDivider: true)
}
}
struct UserLineItem: View {
@EnvironmentObject var transactionDetails: TransactionDetails
let id: String
@EnvironmentObject var dataStore: DataStore
var value: String {
// TODO: Show errors
if case let .success(user) = transactionDetails.user {
if case let .success(user) = dataStore.user, user.id == id {
return user.username
} else {
return ""
@ -168,18 +156,17 @@ struct UserLineItem: View {
@ViewBuilder
var body: some View {
if case .empty = transactionDetails.user {
EmptyView()
} else {
LabeledField(label: "created_by", value: value, loading: .constant(self.value == ""), showDivider: false)
}
LabeledField(label: "created_by", value: value, loading: .constant(self.value == ""), showDivider: false)
.task {
await dataStore.getUser(id)
}
}
}
#if DEBUG
struct TransactionDetailsView_Previews: PreviewProvider {
static var previews: some View {
TransactionDetailsView(TransactionDetails(TwigsInMemoryCacheService()))
TransactionDetailsView()
}
}
#endif

View file

@ -138,7 +138,7 @@ struct TransactionListItemView: View {
tag: self.transaction,
selection: self.$dataStore.selectedTransaction,
destination: {
TransactionDetailsView(TransactionDetails(dataStore.apiService))
TransactionDetailsView()
.navigationBarTitle("details", displayMode: .inline)
},
label: {