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

View file

@ -104,6 +104,14 @@ class DataStore : ObservableObject {
} }
} }
func getBudget(_ id: String) -> TwigsCore.Budget? {
if case let .success(budgets) = self.budgets {
return budgets.first(where: { $0.id == id })
}
return nil
}
func newBudget() { func newBudget() {
self.budget = .editing(Budget(id: "", name: "", description: "", currencyCode: "")) self.budget = .editing(Budget(id: "", name: "", description: "", currencyCode: ""))
} }
@ -253,6 +261,14 @@ class DataStore : ObservableObject {
} }
} }
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 { func save(_ category: TwigsCore.Category) async {
self.category = .loading self.category = .loading
do { do {
@ -731,7 +747,7 @@ class DataStore : ObservableObject {
self.user = .success(user) self.user = .success(user)
} catch { } catch {
self.errorReporter.reportError(error: error) 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 { struct TransactionDetailsView: View {
@EnvironmentObject var apiService: TwigsApiService @EnvironmentObject var apiService: TwigsApiService
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
@ObservedObject var transactionDetails: TransactionDetails
var editing: Bool { var editing: Bool {
if case .editing(_) = dataStore.transaction { if case .editing(_) = dataStore.transaction {
return true return true
@ -24,9 +23,6 @@ struct TransactionDetailsView: View {
return false return false
} }
init(_ transactionDetails: TransactionDetails) {
self.transactionDetails = transactionDetails
}
private var currentUserId: String? { private var currentUserId: String? {
get { get {
if case let .success(currentUser) = self.dataStore.currentUser { if case let .success(currentUser) = self.dataStore.currentUser {
@ -55,14 +51,13 @@ struct TransactionDetailsView: View {
if let description = transaction.description { if let description = transaction.description {
LabeledField(label: "notes", value: description, loading: .constant(false), showDivider: true) LabeledField(label: "notes", value: description, loading: .constant(false), showDivider: true)
} }
CategoryLineItem() if let categoryId = transaction.categoryId {
BudgetLineItem() CategoryLineItem(id: categoryId)
UserLineItem()
}.padding()
.environmentObject(transactionDetails)
.task {
await transactionDetails.loadDetails(transaction)
} }
BudgetLineItem(id: transaction.budgetId)
UserLineItem(id: transaction.createdBy)
}
.padding()
} }
.navigationBarItems(trailing: Button( .navigationBarItems(trailing: Button(
action: { self.dataStore.editTransaction(transaction) } action: { self.dataStore.editTransaction(transaction) }
@ -92,32 +87,31 @@ struct LabeledField: View {
@ViewBuilder @ViewBuilder
var body: some View { var body: some View {
if let val = value, !val.isEmpty { VStack {
VStack { HStack {
HStack { Text(self.label)
Text(self.label) .foregroundColor(.secondary)
.foregroundColor(.secondary) Spacer()
Spacer() if loading {
if loading { EmbeddedLoadingView()
EmbeddedLoadingView() } else {
} else { Text(verbatim: self.value ?? "")
Text(verbatim: val) .multilineTextAlignment(.trailing)
.multilineTextAlignment(.trailing)
}
}
if showDivider {
Divider()
} }
} }
if showDivider {
Divider()
}
} }
} }
} }
struct CategoryLineItem: View { struct CategoryLineItem: View {
@EnvironmentObject var transactionDetails: TransactionDetails let id: String
@EnvironmentObject var dataStore: DataStore
var value: String { var value: String {
// TODO: Show errors // TODO: Show errors
if case let .success(category) = transactionDetails.category { if let category = dataStore.getCategory(id) {
return category.title return category.title
} else { } else {
return "" return ""
@ -126,19 +120,16 @@ struct CategoryLineItem: View {
@ViewBuilder @ViewBuilder
var body: some View { var body: some View {
if case .empty = transactionDetails.category { LabeledField(label: "category", value: value, loading: .constant(self.value == ""), showDivider: true)
EmptyView()
} else {
LabeledField(label: "category", value: value, loading: .constant(self.value == ""), showDivider: true)
}
} }
} }
struct BudgetLineItem: View { struct BudgetLineItem: View {
@EnvironmentObject var transactionDetails: TransactionDetails let id: String
@EnvironmentObject var dataStore: DataStore
var value: String { var value: String {
// TODO: Show errors // TODO: Show errors
if case let .success(budget) = transactionDetails.budget { if let budget = dataStore.getBudget(id) {
return budget.name return budget.name
} else { } else {
return "" return ""
@ -147,19 +138,16 @@ struct BudgetLineItem: View {
@ViewBuilder @ViewBuilder
var body: some View { var body: some View {
if case .empty = transactionDetails.budget { LabeledField(label: "budget", value: value, loading: .constant(self.value == ""), showDivider: true)
EmptyView()
} else {
LabeledField(label: "budget", value: value, loading: .constant(self.value == ""), showDivider: true)
}
} }
} }
struct UserLineItem: View { struct UserLineItem: View {
@EnvironmentObject var transactionDetails: TransactionDetails let id: String
@EnvironmentObject var dataStore: DataStore
var value: String { var value: String {
// TODO: Show errors // TODO: Show errors
if case let .success(user) = transactionDetails.user { if case let .success(user) = dataStore.user, user.id == id {
return user.username return user.username
} else { } else {
return "" return ""
@ -168,18 +156,17 @@ struct UserLineItem: View {
@ViewBuilder @ViewBuilder
var body: some View { var body: some View {
if case .empty = transactionDetails.user { LabeledField(label: "created_by", value: value, loading: .constant(self.value == ""), showDivider: false)
EmptyView() .task {
} else { await dataStore.getUser(id)
LabeledField(label: "created_by", value: value, loading: .constant(self.value == ""), showDivider: false) }
}
} }
} }
#if DEBUG #if DEBUG
struct TransactionDetailsView_Previews: PreviewProvider { struct TransactionDetailsView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
TransactionDetailsView(TransactionDetails(TwigsInMemoryCacheService())) TransactionDetailsView()
} }
} }
#endif #endif

View file

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