WIP: Implement TransactionDetailsView
Signed-off-by: Billy Brawner <billy@wbrawner.com>
This commit is contained in:
parent
c05cb787a3
commit
8fbd43ec5f
21 changed files with 849 additions and 166 deletions
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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 -> Foundation.Data in BudgetApp.RequestHelper.(buildRequest in _49C38939E0AD4FDF6F9F222ABD721B8E)<A where A: Swift.Decodable, A: Swift.Encodable>(endPoint: Swift.String, method: Swift.String, data: Swift.Optional<Swift.Encodable>) -> Combine.AnyPublisher<A, BudgetApp.NetworkError>"
|
||||
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 -> Foundation.Data in BudgetApp.RequestHelper.(buildRequest in _49C38939E0AD4FDF6F9F222ABD721B8E)<A where A: Swift.Decodable, A: Swift.Encodable>(endPoint: Swift.String, method: Swift.String, data: Swift.Optional<Swift.Encodable>) -> Combine.AnyPublisher<A, BudgetApp.NetworkError>"
|
||||
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 -> Foundation.Data in BudgetApp.RequestHelper.(buildRequest in _49C38939E0AD4FDF6F9F222ABD721B8E)<A where A: Swift.Decodable, A: Swift.Encodable>(endPoint: Swift.String, method: Swift.String, data: Swift.Optional<Swift.Encodable>) -> Combine.AnyPublisher<A, BudgetApp.NetworkError>"
|
||||
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 -> Foundation.Data in BudgetApp.RequestHelper.(buildRequest in _49C38939E0AD4FDF6F9F222ABD721B8E)<A where A: Swift.Decodable, A: Swift.Encodable>(endPoint: Swift.String, method: Swift.String, data: Swift.Optional<Swift.Encodable>) -> Combine.AnyPublisher<A, BudgetApp.NetworkError>"
|
||||
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 -> Foundation.Data in BudgetApp.RequestHelper.(buildRequest in _49C38939E0AD4FDF6F9F222ABD721B8E)<A where A: Swift.Decodable, A: Swift.Encodable>(endPoint: Swift.String, method: Swift.String, data: Swift.Optional<Swift.Encodable>) -> Combine.AnyPublisher<A, BudgetApp.NetworkError>"
|
||||
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>
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
20
BudgetApp/Extensions.swift
Normal file
20
BudgetApp/Extensions.swift
Normal 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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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!
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
80
BudgetApp/User/AuthenticationDataStore.swift
Normal file
80
BudgetApp/User/AuthenticationDataStore.swift
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue