WIP: Implement TransactionDetailsView

Signed-off-by: Billy Brawner <billy@wbrawner.com>
This commit is contained in:
Billy Brawner 2019-10-12 18:22:09 -07:00
parent c05cb787a3
commit 8fbd43ec5f
21 changed files with 849 additions and 166 deletions

View file

@ -14,6 +14,7 @@
284102322342E12F00EAFA29 /* CategoryListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 284102312342E12F00EAFA29 /* CategoryListView.swift */; };
2857EAED233DA30B0026BC83 /* LoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2857EAEC233DA30B0026BC83 /* LoadingView.swift */; };
2888234723512DBF003D3847 /* Observable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2888234623512DBF003D3847 /* Observable.swift */; };
289510242352AAFC00BC862B /* UserDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 289510232352AAFC00BC862B /* UserDataStore.swift */; };
28A1E95A235006A300CA57FE /* AddTransactionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A1E959235006A300CA57FE /* AddTransactionView.swift */; };
28AC94EE233C373900BFB70A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28AC94ED233C373900BFB70A /* AppDelegate.swift */; };
28AC94F0233C373900BFB70A /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28AC94EF233C373900BFB70A /* SceneDelegate.swift */; };
@ -29,6 +30,7 @@
28AC952C233C434800BFB70A /* UserRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28AC952B233C434800BFB70A /* UserRepository.swift */; };
28AC952E233C43A300BFB70A /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28AC952D233C43A300BFB70A /* User.swift */; };
28B9E50E2346BCB2007C3909 /* RegistrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28B9E50D2346BCB2007C3909 /* RegistrationView.swift */; };
28CE8B9523525F990072BC4C /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CE8B9423525F990072BC4C /* Extensions.swift */; };
28FE6AF42342E3CB00D5543E /* BudgetsDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FE6AF32342E3CB00D5543E /* BudgetsDataStore.swift */; };
28FE6AF62342E4CC00D5543E /* BudgetRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FE6AF52342E4CC00D5543E /* BudgetRepository.swift */; };
28FE6AF823441E1D00D5543E /* Category.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FE6AF723441E1D00D5543E /* Category.swift */; };
@ -39,7 +41,7 @@
28FE6B022344331B00D5543E /* TransactionDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FE6B012344331B00D5543E /* TransactionDataStore.swift */; };
28FE6B04234449DC00D5543E /* TransactionListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FE6B03234449DC00D5543E /* TransactionListView.swift */; };
28FE6B0623444A9800D5543E /* TransactionDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FE6B0523444A9800D5543E /* TransactionDetailsView.swift */; };
543ECE42233E82A40018A9D9 /* UserDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 543ECE41233E82A40018A9D9 /* UserDataStore.swift */; };
543ECE42233E82A40018A9D9 /* AuthenticationDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 543ECE41233E82A40018A9D9 /* AuthenticationDataStore.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -67,6 +69,7 @@
284102312342E12F00EAFA29 /* CategoryListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryListView.swift; sourceTree = "<group>"; };
2857EAEC233DA30B0026BC83 /* LoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingView.swift; sourceTree = "<group>"; };
2888234623512DBF003D3847 /* Observable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Observable.swift; sourceTree = "<group>"; };
289510232352AAFC00BC862B /* UserDataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDataStore.swift; sourceTree = "<group>"; };
28A1E959235006A300CA57FE /* AddTransactionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddTransactionView.swift; sourceTree = "<group>"; };
28AC94EA233C373900BFB70A /* BudgetApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BudgetApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
28AC94ED233C373900BFB70A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
@ -90,6 +93,7 @@
28AC952B233C434800BFB70A /* UserRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserRepository.swift; sourceTree = "<group>"; };
28AC952D233C43A300BFB70A /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = "<group>"; };
28B9E50D2346BCB2007C3909 /* RegistrationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationView.swift; sourceTree = "<group>"; };
28CE8B9423525F990072BC4C /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
28FE6AF32342E3CB00D5543E /* BudgetsDataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BudgetsDataStore.swift; sourceTree = "<group>"; };
28FE6AF52342E4CC00D5543E /* BudgetRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BudgetRepository.swift; sourceTree = "<group>"; };
28FE6AF723441E1D00D5543E /* Category.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Category.swift; sourceTree = "<group>"; };
@ -100,7 +104,7 @@
28FE6B012344331B00D5543E /* TransactionDataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionDataStore.swift; sourceTree = "<group>"; };
28FE6B03234449DC00D5543E /* TransactionListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionListView.swift; sourceTree = "<group>"; };
28FE6B0523444A9800D5543E /* TransactionDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionDetailsView.swift; sourceTree = "<group>"; };
543ECE41233E82A40018A9D9 /* UserDataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDataStore.swift; sourceTree = "<group>"; };
543ECE41233E82A40018A9D9 /* AuthenticationDataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationDataStore.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -200,6 +204,7 @@
28AC94F5233C373A00BFB70A /* Preview Content */,
28FE6AFD234428BF00D5543E /* DataStoreProvider.swift */,
2888234623512DBF003D3847 /* Observable.swift */,
28CE8B9423525F990072BC4C /* Extensions.swift */,
);
path = BudgetApp;
sourceTree = "<group>";
@ -256,7 +261,8 @@
children = (
28AC952B233C434800BFB70A /* UserRepository.swift */,
28AC952D233C43A300BFB70A /* User.swift */,
543ECE41233E82A40018A9D9 /* UserDataStore.swift */,
543ECE41233E82A40018A9D9 /* AuthenticationDataStore.swift */,
289510232352AAFC00BC862B /* UserDataStore.swift */,
);
path = User;
sourceTree = "<group>";
@ -413,14 +419,16 @@
28B9E50E2346BCB2007C3909 /* RegistrationView.swift in Sources */,
284102322342E12F00EAFA29 /* CategoryListView.swift in Sources */,
284102252341998300EAFA29 /* ContentView.swift in Sources */,
289510242352AAFC00BC862B /* UserDataStore.swift in Sources */,
28FE6B002344308600D5543E /* Transaction.swift in Sources */,
28FE6AF823441E1D00D5543E /* Category.swift in Sources */,
28AC9529233C433400BFB70A /* TransactionRepository.swift in Sources */,
28FE6AF62342E4CC00D5543E /* BudgetRepository.swift in Sources */,
28FE6B022344331B00D5543E /* TransactionDataStore.swift in Sources */,
543ECE42233E82A40018A9D9 /* UserDataStore.swift in Sources */,
543ECE42233E82A40018A9D9 /* AuthenticationDataStore.swift in Sources */,
284102302342D97300EAFA29 /* BudgetListsView.swift in Sources */,
28AC952E233C43A300BFB70A /* User.swift in Sources */,
28CE8B9523525F990072BC4C /* Extensions.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View file

@ -881,5 +881,450 @@
landmarkType = "3">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "0549E6C6-F5DB-41F2-9C12-727E72A30859"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "BudgetApp/Transaction/TransactionDataStore.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "64"
endingLineNumber = "64"
landmarkName = "getTransaction(_:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "7920C444-C874-44EB-907F-E417ABE27E26"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "BudgetApp/Transaction/TransactionDataStore.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "54"
endingLineNumber = "54"
landmarkName = "getTransaction(_:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "CEB7684C-E9B4-4F87-96CD-DC5F2B799FE5"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "BudgetApp/Network/BudgetApiService.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "268"
endingLineNumber = "268"
landmarkName = "buildRequest(endPoint:method:data:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "4657DB47-280A-41EE-B534-09C6F57D8DF8"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "BudgetApp/Network/BudgetApiService.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "267"
endingLineNumber = "267"
landmarkName = "buildRequest(endPoint:method:data:)"
landmarkType = "7">
<Locations>
<Location
uuid = "4657DB47-280A-41EE-B534-09C6F57D8DF8 - 662d42a959a71afc"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "closure #2 (Foundation.Data, __C.NSURLResponse) throws -&gt; Foundation.Data in BudgetApp.RequestHelper.(buildRequest in _49C38939E0AD4FDF6F9F222ABD721B8E)&lt;A where A: Swift.Decodable, A: Swift.Encodable&gt;(endPoint: Swift.String, method: Swift.String, data: Swift.Optional&lt;Swift.Encodable&gt;) -&gt; Combine.AnyPublisher&lt;A, BudgetApp.NetworkError&gt;"
moduleName = "BudgetApp"
usesParentBreakpointCondition = "Yes"
urlString = "file:///Users/billy/Projects/Budget/BudgetApp/Network/BudgetApiService.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "267"
endingLineNumber = "267"
offsetFromSymbolStart = "540">
</Location>
<Location
uuid = "4657DB47-280A-41EE-B534-09C6F57D8DF8 - 662d42a959a71afc"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "closure #2 (Foundation.Data, __C.NSURLResponse) throws -&gt; Foundation.Data in BudgetApp.RequestHelper.(buildRequest in _49C38939E0AD4FDF6F9F222ABD721B8E)&lt;A where A: Swift.Decodable, A: Swift.Encodable&gt;(endPoint: Swift.String, method: Swift.String, data: Swift.Optional&lt;Swift.Encodable&gt;) -&gt; Combine.AnyPublisher&lt;A, BudgetApp.NetworkError&gt;"
moduleName = "BudgetApp"
usesParentBreakpointCondition = "Yes"
urlString = "file:///Users/billy/Projects/Budget/BudgetApp/Network/BudgetApiService.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "267"
endingLineNumber = "267"
offsetFromSymbolStart = "760">
</Location>
<Location
uuid = "4657DB47-280A-41EE-B534-09C6F57D8DF8 - 662d42a959a71afc"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "closure #2 (Foundation.Data, __C.NSURLResponse) throws -&gt; Foundation.Data in BudgetApp.RequestHelper.(buildRequest in _49C38939E0AD4FDF6F9F222ABD721B8E)&lt;A where A: Swift.Decodable, A: Swift.Encodable&gt;(endPoint: Swift.String, method: Swift.String, data: Swift.Optional&lt;Swift.Encodable&gt;) -&gt; Combine.AnyPublisher&lt;A, BudgetApp.NetworkError&gt;"
moduleName = "BudgetApp"
usesParentBreakpointCondition = "Yes"
urlString = "file:///Users/billy/Projects/Budget/BudgetApp/Network/BudgetApiService.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "267"
endingLineNumber = "267"
offsetFromSymbolStart = "928">
</Location>
<Location
uuid = "4657DB47-280A-41EE-B534-09C6F57D8DF8 - 662d42a959a71afc"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "closure #2 (Foundation.Data, __C.NSURLResponse) throws -&gt; Foundation.Data in BudgetApp.RequestHelper.(buildRequest in _49C38939E0AD4FDF6F9F222ABD721B8E)&lt;A where A: Swift.Decodable, A: Swift.Encodable&gt;(endPoint: Swift.String, method: Swift.String, data: Swift.Optional&lt;Swift.Encodable&gt;) -&gt; Combine.AnyPublisher&lt;A, BudgetApp.NetworkError&gt;"
moduleName = "BudgetApp"
usesParentBreakpointCondition = "Yes"
urlString = "file:///Users/billy/Projects/Budget/BudgetApp/Network/BudgetApiService.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "267"
endingLineNumber = "267"
offsetFromSymbolStart = "1004">
</Location>
<Location
uuid = "4657DB47-280A-41EE-B534-09C6F57D8DF8 - 662d42a959a71afc"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "closure #2 (Foundation.Data, __C.NSURLResponse) throws -&gt; Foundation.Data in BudgetApp.RequestHelper.(buildRequest in _49C38939E0AD4FDF6F9F222ABD721B8E)&lt;A where A: Swift.Decodable, A: Swift.Encodable&gt;(endPoint: Swift.String, method: Swift.String, data: Swift.Optional&lt;Swift.Encodable&gt;) -&gt; Combine.AnyPublisher&lt;A, BudgetApp.NetworkError&gt;"
moduleName = "BudgetApp"
usesParentBreakpointCondition = "Yes"
urlString = "file:///Users/billy/Projects/Budget/BudgetApp/Network/BudgetApiService.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "267"
endingLineNumber = "267"
offsetFromSymbolStart = "1176">
</Location>
</Locations>
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "1C526E18-7676-485A-929A-408A7FABB2E6"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "BudgetApp/Network/BudgetApiService.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "271"
endingLineNumber = "271"
landmarkName = "buildRequest(endPoint:method:data:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "6797DFA1-71BC-41AB-AAFB-94156954A707"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "BudgetApp/Transaction/TransactionDataStore.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "60"
endingLineNumber = "60"
landmarkName = "getTransaction(_:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "81B540BD-DA72-4649-9624-E5B94E110061"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "BudgetApp/Network/BudgetApiService.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "278"
endingLineNumber = "278"
landmarkName = "buildRequest(endPoint:method:data:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "CB2BFA16-FD1A-4701-A893-5A35743C2450"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "BudgetApp/Network/BudgetApiService.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "274"
endingLineNumber = "274"
landmarkName = "buildRequest(endPoint:method:data:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "BF36AB05-00B6-4DDF-B0EA-6262C5AD8534"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "BudgetApp/Budget/BudgetsDataStore.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "56"
endingLineNumber = "56"
landmarkName = "getBudget(_:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "22BC67DA-8703-4CF0-AB4E-68895A58E9D6"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "BudgetApp/Budget/BudgetsDataStore.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "48"
endingLineNumber = "48"
landmarkName = "getBudget(_:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "9BCA6167-F538-4216-9D4E-1003FC3506D0"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "BudgetApp/Transaction/TransactionDetailsView.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "131"
endingLineNumber = "131"
landmarkName = "stateContent"
landmarkType = "24">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "20183989-A777-46A3-BA5B-064428652CCA"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "BudgetApp/Transaction/TransactionDetailsView.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "132"
endingLineNumber = "132"
landmarkName = "stateContent"
landmarkType = "24">
<Locations>
<Location
uuid = "20183989-A777-46A3-BA5B-064428652CCA - 4789c2d7af74bffe"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "BudgetApp.UserLineItem.stateContent.getter : SwiftUI.AnyView"
moduleName = "BudgetApp"
usesParentBreakpointCondition = "Yes"
urlString = "file:///Users/billy/Projects/Budget/BudgetApp/Transaction/TransactionDetailsView.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "132"
endingLineNumber = "132"
offsetFromSymbolStart = "109">
</Location>
<Location
uuid = "20183989-A777-46A3-BA5B-064428652CCA - 4789c2d7af74bffe"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "BudgetApp.UserLineItem.stateContent.getter : SwiftUI.AnyView"
moduleName = "BudgetApp"
usesParentBreakpointCondition = "Yes"
urlString = "file:///Users/billy/Projects/Budget/BudgetApp/Transaction/TransactionDetailsView.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "132"
endingLineNumber = "132"
offsetFromSymbolStart = "145">
</Location>
</Locations>
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "2C303047-3F10-47B2-8111-569D237CAB67"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "BudgetApp/Transaction/TransactionDetailsView.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "109"
endingLineNumber = "109"
landmarkName = "stateContent"
landmarkType = "24">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "0233ED78-5BA5-443B-B675-45AAB46BAB8F"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "BudgetApp/Transaction/TransactionDetailsView.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "110"
endingLineNumber = "110"
landmarkName = "stateContent"
landmarkType = "24">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "EDF8EF50-D051-4718-8721-9DE8F9B65084"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "BudgetApp/Transaction/TransactionDetailsView.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "85"
endingLineNumber = "85"
landmarkName = "stateContent"
landmarkType = "24">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "EF57623B-CD5D-4FDD-95A7-71C9377F8DD9"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "BudgetApp/Transaction/TransactionDetailsView.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "86"
endingLineNumber = "86"
landmarkName = "stateContent"
landmarkType = "24">
<Locations>
<Location
uuid = "EF57623B-CD5D-4FDD-95A7-71C9377F8DD9 - d801080647ddb01b"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "BudgetApp.CategoryLineItem.stateContent.getter : SwiftUI.AnyView"
moduleName = "BudgetApp"
usesParentBreakpointCondition = "Yes"
urlString = "file:///Users/billy/Projects/Budget/BudgetApp/Transaction/TransactionDetailsView.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "86"
endingLineNumber = "86"
offsetFromSymbolStart = "112">
</Location>
<Location
uuid = "EF57623B-CD5D-4FDD-95A7-71C9377F8DD9 - d801080647ddb01b"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "BudgetApp.CategoryLineItem.stateContent.getter : SwiftUI.AnyView"
moduleName = "BudgetApp"
usesParentBreakpointCondition = "Yes"
urlString = "file:///Users/billy/Projects/Budget/BudgetApp/Transaction/TransactionDetailsView.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "86"
endingLineNumber = "86"
offsetFromSymbolStart = "148">
</Location>
</Locations>
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "5D6D7CF4-551C-4B2E-A47C-01A1321CCD2C"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "BudgetApp/Transaction/TransactionDetailsView.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "87"
endingLineNumber = "87"
landmarkName = "stateContent"
landmarkType = "24">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "B94A3F1C-C557-4C9C-90A5-666559DE4E6F"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "BudgetApp/Transaction/TransactionDetailsView.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "111"
endingLineNumber = "111"
landmarkName = "stateContent"
landmarkType = "24">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "EB46A487-FDF7-4C7D-9592-AA482A2B3D32"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "BudgetApp/Transaction/TransactionDetailsView.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "133"
endingLineNumber = "133"
landmarkName = "stateContent"
landmarkType = "24">
</BreakpointContent>
</BreakpointProxy>
</Breakpoints>
</Bucket>

View file

@ -53,22 +53,19 @@ class NetworkBudgetRepository: BudgetRepository {
#if DEBUG
class MockBudgetRepository: BudgetRepository {
static let budget = Budget(
id: 1,
name: "Test Budget",
description: "A mock budget used for testing",
users: []
)
func getBudgets(count: Int?, page: Int?) -> AnyPublisher<[Budget], NetworkError> {
return Result.Publisher([Budget(
id: 1,
name: "Test Budget",
description: "A mock budget used for testing",
users: []
)]).eraseToAnyPublisher()
return Result.Publisher([MockBudgetRepository.budget]).eraseToAnyPublisher()
}
func getBudget(_ id: Int) -> AnyPublisher<Budget, NetworkError> {
return Result.Publisher(Budget(
id: 1,
name: "Test Budget",
description: "A mock budget used for testing",
users: []
)).eraseToAnyPublisher()
return Result.Publisher(MockBudgetRepository.budget).eraseToAnyPublisher()
}
func getBudgetBalance(_ id: Int) -> AnyPublisher<Int, NetworkError> {
@ -76,12 +73,7 @@ class MockBudgetRepository: BudgetRepository {
}
func newBudget(_ budget: Budget) -> AnyPublisher<Budget, NetworkError> {
return Result.Publisher(Budget(
id: 1,
name: "Test Budget",
description: "A mock budget used for testing",
users: []
)).eraseToAnyPublisher()
return Result.Publisher(MockBudgetRepository.budget).eraseToAnyPublisher()
}
func updateBudget(_ budget: Budget) -> AnyPublisher<Budget, NetworkError> {

View file

@ -20,7 +20,7 @@ class BudgetsDataStore: ObservableObject {
self.objectWillChange.send()
}
}
func getBudgets(count: Int? = nil, page: Int? = nil) {
self.budgets = .failure(.loading)
@ -39,6 +39,24 @@ class BudgetsDataStore: ObservableObject {
})
}
func getBudget(_ id: Int) {
self.budget = .failure(.loading)
_ = self.budgetRepository.getBudget(id)
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { (status) in
switch status {
case .finished:
return
case .failure(let error):
self.budget = .failure(error)
return
}
}, receiveValue: { (budget) in
self.budget = .success(budget)
})
}
init(_ budgetRepository: BudgetRepository) {
self.budgetRepository = budgetRepository
}

View file

@ -16,6 +16,12 @@ class CategoryDataStore: ObservableObject {
}
}
var category: Result<Category, NetworkError> = .failure(.loading) {
didSet {
self.objectWillChange.send()
}
}
func getCategories(budgetId: Int? = nil, count: Int? = nil, page: Int? = nil) {
self.categories = .failure(.loading)
@ -33,6 +39,23 @@ class CategoryDataStore: ObservableObject {
})
}
func getCategory(_ categoryId: Int) {
self.category = .failure(.loading)
_ = categoryRepository.getCategory(categoryId)
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { (completion) in
switch completion {
case .finished:
return
case .failure(let error):
self.category = .failure(error)
}
}, receiveValue: { (categories) in
self.category = .success(categories)
})
}
let objectWillChange = ObservableObjectPublisher()
private let categoryRepository: CategoryRepository
init(_ categoryRepository: CategoryRepository) {

View file

@ -11,6 +11,7 @@ import Combine
protocol CategoryRepository {
func getCategories(budgetId: Int?, count: Int?, page: Int?) -> AnyPublisher<[Category], NetworkError>
func getCategory(_ categoryId: Int) -> AnyPublisher<Category, NetworkError>
}
class NetworkCategoryRepository: CategoryRepository {
@ -23,13 +24,29 @@ class NetworkCategoryRepository: CategoryRepository {
func getCategories(budgetId: Int?, count: Int?, page: Int?) -> AnyPublisher<[Category], NetworkError> {
return apiService.getCategories(budgetId: budgetId, count: count, page: page)
}
func getCategory(_ categoryId: Int) -> AnyPublisher<Category, NetworkError> {
return apiService.getCategory(categoryId)
}
}
#if DEBUG
class MockCategoryRepository: CategoryRepository {
static let category = Category(
budgetId: MockBudgetRepository.budget.id!,
id: 3,
title: "Test Category",
description: "This is a test category to help with testing",
amount: 10000
)
func getCategories(budgetId: Int?, count: Int?, page: Int?) -> AnyPublisher<[Category], NetworkError> {
return Result.Publisher([]).eraseToAnyPublisher()
return Result.Publisher([MockCategoryRepository.category]).eraseToAnyPublisher()
}
func getCategory(_ categoryId: Int) -> AnyPublisher<Category, NetworkError> {
return Result.Publisher(MockCategoryRepository.category).eraseToAnyPublisher()
}
}

View file

@ -9,7 +9,7 @@
import SwiftUI
struct ContentView: View {
@ObservedObject var userData: UserDataStore
@ObservedObject var authenticationDataStore: AuthenticationDataStore
var body: some View {
stateContent
@ -17,14 +17,14 @@ struct ContentView: View {
var stateContent: AnyView {
if showLogin() {
return AnyView(LoginView(userData))
return AnyView(LoginView(authenticationDataStore))
} else {
return AnyView(TabbedBudgetView(userData, dataStoreProvider: dataStoreProvider))
return AnyView(TabbedBudgetView(authenticationDataStore, dataStoreProvider: dataStoreProvider))
}
}
func showLogin() -> Bool {
switch userData.currentUser {
switch authenticationDataStore.currentUser {
case .failure:
return true
default:
@ -36,7 +36,7 @@ struct ContentView: View {
init (_ dataStoreProvider: DataStoreProvider) {
self.dataStoreProvider = dataStoreProvider
self.userData = dataStoreProvider.userDataStore()
self.authenticationDataStore = dataStoreProvider.authenticationDataStore()
}
}

View file

@ -15,8 +15,9 @@ class DataStoreProvider {
private let budgetRepository: BudgetRepository
private let categoryRepository: CategoryRepository
private let transactionRepository: TransactionRepository
private let _userDataStore: UserDataStore
private let userRepository: UserRepository
private let _authenticationDataStore: AuthenticationDataStore
func budgetsDataStore() -> BudgetsDataStore {
return BudgetsDataStore(budgetRepository)
@ -30,8 +31,12 @@ class DataStoreProvider {
return TransactionDataStore(transactionRepository)
}
func authenticationDataStore() -> AuthenticationDataStore {
return self._authenticationDataStore
}
func userDataStore() -> UserDataStore {
return self._userDataStore
return UserDataStore(userRepository)
}
init(
@ -43,7 +48,8 @@ class DataStoreProvider {
self.budgetRepository = budgetRepository
self.categoryRepository = categoryRepository
self.transactionRepository = transactionRepository
self._userDataStore = UserDataStore(userRepository)
self.userRepository = userRepository
self._authenticationDataStore = AuthenticationDataStore(userRepository)
}
}

View file

@ -0,0 +1,20 @@
//
// Extensions.swift
// BudgetApp
//
// Created by Billy Brawner on 10/12/19.
// Copyright © 2019 William Brawner. All rights reserved.
//
import Foundation
extension Int {
func toCurrencyString() -> String? {
let currencyFormatter = NumberFormatter()
currencyFormatter.locale = Locale.current
currencyFormatter.numberStyle = .currency
let doubleSelf = Double(self) / 100.0
return currencyFormatter.string(from: NSNumber(value: doubleSelf))
}
}

View file

@ -12,7 +12,7 @@ import Combine
struct LoginView: View {
@State var username: String = ""
@State var password: String = ""
@ObservedObject var userData: UserDataStore
@ObservedObject var userData: AuthenticationDataStore
let showLoader: Bool
var body: some View {
@ -43,7 +43,7 @@ struct LoginView: View {
}
}
init (_ userData: UserDataStore) {
init (_ userData: AuthenticationDataStore) {
self.userData = userData
if case userData.currentUser = Result<User, UserStatus>.failure(UserStatus.authenticating) {
self.showLoader = true

View file

@ -13,7 +13,7 @@ struct RegistrationView: View {
@State var email: String = ""
@State var password: String = ""
@State var confirmedPassword: String = ""
@ObservedObject var userData: UserDataStore
@ObservedObject var userData: AuthenticationDataStore
var body: some View {
VStack {
@ -42,7 +42,7 @@ struct RegistrationView: View {
}.padding()
}
init(_ userData: UserDataStore) {
init(_ userData: AuthenticationDataStore) {
self.userData = userData
}
}

View file

@ -9,7 +9,7 @@
import SwiftUI
struct TabbedBudgetView: View {
@ObservedObject var userData: UserDataStore
@ObservedObject var userData: AuthenticationDataStore
@State var isAddingTransaction = false
var body: some View {
@ -49,7 +49,7 @@ struct TabbedBudgetView: View {
}
let dataStoreProvider: DataStoreProvider
init (_ userData: UserDataStore, dataStoreProvider: DataStoreProvider) {
init (_ userData: AuthenticationDataStore, dataStoreProvider: DataStoreProvider) {
self.userData = userData
self.dataStoreProvider = dataStoreProvider
}

View file

@ -93,14 +93,13 @@ struct AddTransactionView: View {
},
trailing: Button("save") {
let amount = Double(self.amount) ?? 0.0
self.transactionDataStore.createTransaction(Transaction(
id: self.id,
title: self.title,
description: self.description,
date: self.date,
amount: Int(amount * 100.0),
categoryId: self.categoryId!,
categoryId: self.categoryId,
expense: self.type == TransactionType.expense,
createdBy: self.createdBy,
budgetId: self.budgetPublisher.value!
@ -119,7 +118,7 @@ struct AddTransactionView: View {
self.budgetsDataStore = budgetsDataStore
let categoryDataStore = dataStoreProvider.categoryDataStore()
self.categoryDataStore = categoryDataStore
self.createdBy = try! dataStoreProvider.userDataStore().currentUser.get().id!
self.createdBy = try! dataStoreProvider.authenticationDataStore().currentUser.get().id!
}
}

View file

@ -16,26 +16,136 @@ struct TransactionDetailsView: View {
var stateContent: AnyView {
switch transactionDataStore.transaction {
case .success(let transaction):
return AnyView(VStack {
Text(transaction.title)
})
return AnyView(ScrollView {
VStack(alignment: .leading) {
Text(transaction.title)
.font(.title)
Text(transaction.amount.toCurrencyString() ?? "")
.font(.headline)
.foregroundColor(transaction.expense ? .red : .green)
.multilineTextAlignment(.trailing)
Spacer().frame(height: 10)
Text(transaction.date.toLocaleString())
.font(.subheadline)
.foregroundColor(.secondary)
Spacer().frame(height: 20.0)
LabeledField(label: "notes", value: transaction.description, showDivider: true)
CategoryLineItem(self.dataStoreProvider, categoryId: transaction.categoryId)
BudgetLineItem(self.dataStoreProvider, budgetId: transaction.budgetId)
UserLineItem(self.dataStoreProvider, userId: transaction.createdBy)
}.padding()
}
.navigationBarItems(trailing: NavigationLink(destination: EmptyView()) {
Text("edit")
}))
case .failure(.loading):
return AnyView(EmbeddedLoadingView())
default:
return AnyView(Text("transaction_details_error"))
}
}
let transactionDataStore: TransactionDataStore
init(_ dataStoreProvider: DataStoreProvider, transaction: Transaction) {
let dataStoreProvider: DataStoreProvider
@ObservedObject var transactionDataStore: TransactionDataStore
init(_ dataStoreProvider: DataStoreProvider, transactionId: Int) {
self.dataStoreProvider = dataStoreProvider
let transactionDataStore = dataStoreProvider.transactionDataStore()
transactionDataStore.getTransactions()
transactionDataStore.getTransaction(transactionId)
self.transactionDataStore = transactionDataStore
}
}
//struct TransactionDetailsView_Previews: PreviewProvider {
// static var previews: some View {
// TransactionDetailsView()
// }
//}
struct LabeledField: View {
let label: LocalizedStringKey
let value: String?
let showDivider: Bool
var body: some View {
VStack {
HStack {
Text(self.label)
.foregroundColor(.secondary)
Spacer()
Text(verbatim: value ?? "")
.multilineTextAlignment(.trailing)
}
if showDivider {
Divider()
}
}
}
}
struct CategoryLineItem: View {
var body: some View {
stateContent
}
var stateContent: AnyView {
switch categoryDataStore.category {
case .success(let category):
return AnyView(LabeledField(label: "category", value: category.title, showDivider: true))
default:
return AnyView(LabeledField(label: "category", value: "", showDivider: true))
}
}
@ObservedObject var categoryDataStore: CategoryDataStore
init(_ dataStoreProvider: DataStoreProvider, categoryId: Int?) {
let categoryDataStore = dataStoreProvider.categoryDataStore()
if let id = categoryId {
categoryDataStore.getCategory(id)
}
self.categoryDataStore = categoryDataStore
}
}
struct BudgetLineItem: View {
var body: some View {
stateContent
}
var stateContent: AnyView {
switch budgetDataStore.budget {
case .success(let budget):
return AnyView(LabeledField(label: "budget", value: budget.name, showDivider: true))
default:
return AnyView(LabeledField(label: "budget", value: "", showDivider: true))
}
}
@ObservedObject var budgetDataStore: BudgetsDataStore
init(_ dataStoreProvider: DataStoreProvider, budgetId: Int) {
let budgetDataStore = dataStoreProvider.budgetsDataStore()
budgetDataStore.getBudget(budgetId)
self.budgetDataStore = budgetDataStore
}
}
struct UserLineItem: View {
var body: some View {
stateContent
}
var stateContent: AnyView {
switch userDataStore.user {
case .success(let user):
return AnyView(LabeledField(label: "registered_by", value: user.username, showDivider: false))
default:
return AnyView(LabeledField(label: "registered_by", value: "", showDivider: false))
}
}
@ObservedObject var userDataStore: UserDataStore
init(_ dataStoreProvider: DataStoreProvider, userId: Int) {
let userDataStore = dataStoreProvider.userDataStore()
userDataStore.getUser(userId)
self.userDataStore = userDataStore
}
}
struct TransactionDetailsView_Previews: PreviewProvider {
static var previews: some View {
TransactionDetailsView(MockDataStoreProvider(), transactionId: 2)
}
}

View file

@ -47,12 +47,11 @@ struct TransactionListView: View {
struct TransactionListItemView: View {
var transaction: Transaction
let dataStoreProvider: DataStoreProvider
let numberFormatter: NumberFormatter
var body: some View {
NavigationLink(
destination: TransactionDetailsView(self.dataStoreProvider, transaction: transaction)
.navigationBarTitle(transaction.title)
destination: TransactionDetailsView(self.dataStoreProvider, transactionId: transaction.id!)
.navigationBarTitle("details", displayMode: .inline)
) {
HStack {
VStack(alignment: .leading) {
@ -67,7 +66,7 @@ struct TransactionListItemView: View {
}
Spacer()
VStack(alignment: .trailing) {
Text(verbatim: self.numberFormatter.string(from: NSNumber(value: Double(transaction.amount) / 100.0)) ?? "")
Text(verbatim: transaction.amount.toCurrencyString() ?? "")
.foregroundColor(transaction.expense ? .red : .green)
.multilineTextAlignment(.trailing)
}
@ -79,9 +78,5 @@ struct TransactionListItemView: View {
init (_ dataStoreProvider: DataStoreProvider, transaction: Transaction) {
self.dataStoreProvider = dataStoreProvider
self.transaction = transaction
let formatter = NumberFormatter()
formatter.locale = Locale.current
formatter.numberStyle = .currency
self.numberFormatter = formatter
}
}

View file

@ -37,46 +37,28 @@ class NetworkTransactionRepository: TransactionRepository {
#if DEBUG
class MockTransactionRepository: TransactionRepository {
func getTransactions(categoryIds: [Int]? = nil, from: Date? = nil, count: Int? = nil, page: Int? = nil) -> AnyPublisher<[Transaction], NetworkError> {
return Result.Publisher([Transaction(
id: 2,
title: "Test Transaction",
description: "A mock transaction used for testing",
date: Date(),
amount: 10000,
categoryId: 3,
expense: true,
createdBy: 0,
budgetId: 1
)]).eraseToAnyPublisher()
static let transaction: Transaction = Transaction(
id: 2,
title: "Test Transaction",
description: "A mock transaction used for testing",
date: Date(),
amount: 10000,
categoryId: MockCategoryRepository.category.id!,
expense: true,
createdBy: MockUserRepository.user.id!,
budgetId: MockBudgetRepository.budget.id!
)
func getTransactions(categoryIds: [Int]?, from: Date?, count: Int?, page: Int?) -> AnyPublisher<[Transaction], NetworkError> {
return Result.Publisher([MockTransactionRepository.transaction]).eraseToAnyPublisher()
}
func getTransaction(_ transactionId: Int) -> AnyPublisher<Transaction, NetworkError> {
return Result.Publisher(Transaction(
id: 2,
title: "Test Transaction",
description: "A mock transaction used for testing",
date: Date(),
amount: 10000,
categoryId: 3,
expense: true,
createdBy: 0,
budgetId: 1
)).eraseToAnyPublisher()
return Result.Publisher(MockTransactionRepository.transaction).eraseToAnyPublisher()
}
func createTransaction(_ transaction: Transaction) -> AnyPublisher<Transaction, NetworkError> {
return Result.Publisher(Transaction(
id: 2,
title: "Test Transaction",
description: "A mock transaction used for testing",
date: Date(),
amount: 10000,
categoryId: 3,
expense: true,
createdBy: 0,
budgetId: 1
)).eraseToAnyPublisher()
return Result.Publisher(MockTransactionRepository.transaction).eraseToAnyPublisher()
}
}
#endif

View file

@ -0,0 +1,80 @@
import Foundation
import Combine
class AuthenticationDataStore: ObservableObject {
var currentUser: Result<User, UserStatus> = .failure(.unauthenticated) {
didSet {
self.objectWillChange.send()
}
}
func login(username: String, password: String) {
// Changes the status and notifies any observers of the change
self.currentUser = .failure(.authenticating)
// Perform the login
_ = self.userRepository.login(username: username, password: password)
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { (status) in
switch status {
case .finished:
return
// Do nothing it means the network request just ended
case .failure(let error):
switch error {
case .jsonParsingFailed(let jsonError):
print(jsonError.localizedDescription)
default:
print(error.localizedDescription)
}
// Poulate your status with failed authenticating
self.currentUser = .failure(.failedAuthentication)
}
}) { (user) in
self.currentUser = .success(user)
}
}
func register(username: String, email: String, password: String, confirmPassword: String) {
self.currentUser = .failure(.authenticating)
// TODO: Validate other fields as well
if !password.elementsEqual(confirmPassword) {
self.currentUser = .failure(.passwordMismatch)
return
}
_ = self.userRepository.register(username: username, email: email, password: password)
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { (status) in
switch status {
case .finished:
return
// Do nothing it means the network request just ended
case .failure( _):
// Poulate your status with failed authenticating
self.currentUser = .failure(.failedAuthentication)
}
}) { (user) in
self.currentUser = .success(user)
}
}
init(_ userRepository: UserRepository) {
self.userRepository = userRepository
}
// Needed since the default implementation is currently broken
let objectWillChange = ObservableObjectPublisher()
private let userRepository: UserRepository
}
enum UserStatus: Error, Equatable {
case unauthenticated
case authenticating
case failedAuthentication
case authenticated
case passwordMismatch // Passwords don't match
}

View file

@ -1,65 +1,39 @@
//
// UserDataStore.swift
// BudgetApp
//
// Created by Billy Brawner on 10/12/19.
// Copyright © 2019 William Brawner. All rights reserved.
//
import Foundation
import Combine
class UserDataStore: ObservableObject {
var currentUser: Result<User, UserStatus> = .failure(.unauthenticated) {
var user: Result<User, NetworkError> = .failure(.loading) {
didSet {
self.objectWillChange.send()
}
}
func login(username: String, password: String) {
func getUser(_ id: Int) {
self.user = .failure(.loading)
// Changes the status and notifies any observers of the change
self.currentUser = .failure(.authenticating)
// Perform the login
_ = self.userRepository.login(username: username, password: password)
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { (status) in
switch status {
case .finished:
return
// Do nothing it means the network request just ended
case .failure(let error):
switch error {
case .jsonParsingFailed(let jsonError):
print(jsonError.localizedDescription)
default:
print(error.localizedDescription)
}
// Poulate your status with failed authenticating
self.currentUser = .failure(.failedAuthentication)
}
}) { (user) in
self.currentUser = .success(user)
}
}
func register(username: String, email: String, password: String, confirmPassword: String) {
self.currentUser = .failure(.authenticating)
// TODO: Validate other fields as well
if !password.elementsEqual(confirmPassword) {
self.currentUser = .failure(.passwordMismatch)
return
}
_ = self.userRepository.register(username: username, email: email, password: password)
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { (status) in
switch status {
case .finished:
return
// Do nothing it means the network request just ended
case .failure( _):
// Poulate your status with failed authenticating
self.currentUser = .failure(.failedAuthentication)
}
}) { (user) in
self.currentUser = .success(user)
}
_ = userRepository.getUser(id)
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { (status) in
switch status {
case .finished:
return
case .failure(let error):
self.user = .failure(error)
return
}
}, receiveValue: { (user) in
self.user = .success(user)
})
}
init(_ userRepository: UserRepository) {
@ -70,11 +44,3 @@ class UserDataStore: ObservableObject {
let objectWillChange = ObservableObjectPublisher()
private let userRepository: UserRepository
}
enum UserStatus: Error, Equatable {
case unauthenticated
case authenticating
case failedAuthentication
case authenticated
case passwordMismatch // Passwords don't match
}

View file

@ -10,8 +10,8 @@ import Foundation
import Combine
protocol UserRepository {
func getUser(id: Int) -> AnyPublisher<User, NetworkError>
func searchUsers(withUsername: String) -> AnyPublisher<[User], NetworkError>
func getUser(_ id: Int) -> AnyPublisher<User, NetworkError>
func searchUsers(_ withUsername: String) -> AnyPublisher<[User], NetworkError>
func login(username: String, password: String) -> AnyPublisher<User, NetworkError>
func register(username: String, email: String, password: String) -> AnyPublisher<User, NetworkError>
}
@ -23,11 +23,11 @@ class NetworkUserRepository: UserRepository {
self.apiService = apiService
}
func getUser(id: Int) -> AnyPublisher<User, NetworkError> {
func getUser(_ id: Int) -> AnyPublisher<User, NetworkError> {
return apiService.getUser(id: id)
}
func searchUsers(withUsername: String) -> AnyPublisher<[User], NetworkError> {
func searchUsers(_ withUsername: String) -> AnyPublisher<[User], NetworkError> {
return apiService.searchUsers(query: withUsername)
}
@ -43,23 +43,25 @@ class NetworkUserRepository: UserRepository {
#if DEBUG
class MockUserRepository: UserRepository {
func getUser(id: Int) -> AnyPublisher<User, NetworkError> {
return Result<User, NetworkError>.Publisher(.failure(NetworkError.unknown))
static let user = User(id: 0, username: "root", email: "root@localhost", avatar: nil)
func getUser(_ id: Int) -> AnyPublisher<User, NetworkError> {
return Result<User, NetworkError>.Publisher(MockUserRepository.user)
.eraseToAnyPublisher()
}
func searchUsers(withUsername: String) -> AnyPublisher<[User], NetworkError> {
return Result<[User], NetworkError>.Publisher(.failure(NetworkError.unknown))
func searchUsers(_ withUsername: String) -> AnyPublisher<[User], NetworkError> {
return Result<[User], NetworkError>.Publisher([MockUserRepository.user])
.eraseToAnyPublisher()
}
func login(username: String, password: String) -> AnyPublisher<User, NetworkError> {
return Result<User, NetworkError>.Publisher(.failure(NetworkError.unknown))
return Result<User, NetworkError>.Publisher(MockUserRepository.user)
.eraseToAnyPublisher()
}
func register(username: String, email: String, password: String) -> AnyPublisher<User, NetworkError> {
return Result<User, NetworkError>.Publisher(.failure(NetworkError.unknown))
return Result<User, NetworkError>.Publisher(MockUserRepository.user)
.eraseToAnyPublisher()
}
}

View file

@ -9,8 +9,10 @@
// MARK: Generic
"add" = "Add";
"filter" = "Filter";
"edit" = "Edit";
"save" = "Save";
"cancel" = "Cancel";
"details" = "Details";
"loading_default" = "Loading...";
"prompt_name" = "Name";
"prompt_description" = "Description";
@ -21,6 +23,7 @@
"prompt_type" = "Type";
"type_income" = "Income";
"type_expense" = "Expense";
"retry" = "Retry";
// MARK: Login
"info_login" = "Login to start managing your budget";
@ -38,8 +41,15 @@
// MARK: Transactions
"transactions" = "Transactions";
"add_transaction" = "Add Transaction";
"notes" = "Notes";
"registered_by" = "Registered by";
// MARK: Categories
"category" = "Category";
"categories" = "Categories";
// MARK: Budgets
"budget" = "Budget";
"budgets" = "Budgets";
// MARK: Profile

View file

@ -9,8 +9,10 @@
// MARK: Generic
"add" = "Agregar";
"filter" = "Filtrar";
"edit" = "Editar";
"save" = "Guardar";
"cancel" = "Cancelar";
"details" = "Detalles";
"loading_default" = "Cargando...";
"prompt_name" = "Nombre";
"prompt_description" = "Descripción";
@ -21,6 +23,7 @@
"prompt_type" = "Tipo";
"type_income" = "Ingreso";
"type_expense" = "Gasto";
"retry" = "Volver a intentar";
// MARK: Login
"info_login" = "Inicia sesión para empezar a manejar su presupuseto";
@ -38,8 +41,15 @@
// MARK: Transactions
"transactions" = "Transacciones";
"add_transaction" = "Agregar Transacción";
"notes" = "Notas";
"registered_by" = "Registrado por";
// MARK: Categories
"category" = "Categoría";
"categories" = "Categorías";
// MARK: Budgets
"budget" = "Presupuesto";
"budgets" = "Presupuestos";
// MARK: Profile