Implement some caching and session persistence
Signed-off-by: Billy Brawner <billy@wbrawner.com>
This commit is contained in:
parent
9a26790cda
commit
84bb2e2176
20 changed files with 673 additions and 47 deletions
|
@ -9,6 +9,8 @@
|
|||
/* Begin PBXBuildFile section */
|
||||
2821266023555FD300072D52 /* EditTransactionForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2821265F23555FD300072D52 /* EditTransactionForm.swift */; };
|
||||
282126622357E45F00072D52 /* TransactionEditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 282126612357E45F00072D52 /* TransactionEditView.swift */; };
|
||||
282126A1235929B800072D52 /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 282126A0235929B800072D52 /* ProfileView.swift */; };
|
||||
282126A3235ABC1800072D52 /* BudgetAppInMemoryCacheService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 282126A2235ABC1800072D52 /* BudgetAppInMemoryCacheService.swift */; };
|
||||
284102252341998300EAFA29 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 284102242341998300EAFA29 /* ContentView.swift */; };
|
||||
2841022723419A2B00EAFA29 /* TabbedBudgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2841022623419A2B00EAFA29 /* TabbedBudgetView.swift */; };
|
||||
2841022C2342D8E400EAFA29 /* Budget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2841022B2342D8E400EAFA29 /* Budget.swift */; };
|
||||
|
@ -27,7 +29,7 @@
|
|||
28AC9505233C373A00BFB70A /* BudgetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28AC9504233C373A00BFB70A /* BudgetTests.swift */; };
|
||||
28AC9510233C373A00BFB70A /* BudgetUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28AC950F233C373A00BFB70A /* BudgetUITests.swift */; };
|
||||
28AC951F233C381C00BFB70A /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 28AC9521233C381C00BFB70A /* Localizable.strings */; };
|
||||
28AC9525233C42D100BFB70A /* BudgetApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28AC9524233C42D100BFB70A /* BudgetApiService.swift */; };
|
||||
28AC9525233C42D100BFB70A /* BudgetAppApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28AC9524233C42D100BFB70A /* BudgetAppApiService.swift */; };
|
||||
28AC9529233C433400BFB70A /* TransactionRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28AC9528233C433400BFB70A /* TransactionRepository.swift */; };
|
||||
28AC952C233C434800BFB70A /* UserRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28AC952B233C434800BFB70A /* UserRepository.swift */; };
|
||||
28AC952E233C43A300BFB70A /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28AC952D233C43A300BFB70A /* User.swift */; };
|
||||
|
@ -66,6 +68,8 @@
|
|||
/* Begin PBXFileReference section */
|
||||
2821265F23555FD300072D52 /* EditTransactionForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditTransactionForm.swift; sourceTree = "<group>"; };
|
||||
282126612357E45F00072D52 /* TransactionEditView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionEditView.swift; sourceTree = "<group>"; };
|
||||
282126A0235929B800072D52 /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; };
|
||||
282126A2235ABC1800072D52 /* BudgetAppInMemoryCacheService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BudgetAppInMemoryCacheService.swift; sourceTree = "<group>"; };
|
||||
284102242341998300EAFA29 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||
2841022623419A2B00EAFA29 /* TabbedBudgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabbedBudgetView.swift; sourceTree = "<group>"; };
|
||||
2841022B2342D8E400EAFA29 /* Budget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Budget.swift; sourceTree = "<group>"; };
|
||||
|
@ -92,7 +96,7 @@
|
|||
28AC9520233C381C00BFB70A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
28AC9522233C384C00BFB70A /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/LaunchScreen.strings"; sourceTree = "<group>"; };
|
||||
28AC9523233C384C00BFB70A /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
28AC9524233C42D100BFB70A /* BudgetApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BudgetApiService.swift; sourceTree = "<group>"; };
|
||||
28AC9524233C42D100BFB70A /* BudgetAppApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BudgetAppApiService.swift; sourceTree = "<group>"; };
|
||||
28AC9528233C433400BFB70A /* TransactionRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionRepository.swift; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
|
@ -136,6 +140,14 @@
|
|||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
2821269F2359299D00072D52 /* Profile */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
282126A0235929B800072D52 /* ProfileView.swift */,
|
||||
);
|
||||
path = Profile;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
284102292342D8BB00EAFA29 /* Budget */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -189,6 +201,7 @@
|
|||
28AC94EC233C373900BFB70A /* BudgetApp */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2821269F2359299D00072D52 /* Profile */,
|
||||
2841022A2342D8CB00EAFA29 /* Category */,
|
||||
284102292342D8BB00EAFA29 /* Budget */,
|
||||
2857EAEB233DA2F90026BC83 /* Views */,
|
||||
|
@ -257,7 +270,8 @@
|
|||
28AC9527233C430A00BFB70A /* Network */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
28AC9524233C42D100BFB70A /* BudgetApiService.swift */,
|
||||
28AC9524233C42D100BFB70A /* BudgetAppApiService.swift */,
|
||||
282126A2235ABC1800072D52 /* BudgetAppInMemoryCacheService.swift */,
|
||||
);
|
||||
path = Network;
|
||||
sourceTree = "<group>";
|
||||
|
@ -420,9 +434,10 @@
|
|||
28AC952C233C434800BFB70A /* UserRepository.swift in Sources */,
|
||||
28FE6B04234449DC00D5543E /* TransactionListView.swift in Sources */,
|
||||
28AC94F2233C373900BFB70A /* LoginView.swift in Sources */,
|
||||
28AC9525233C42D100BFB70A /* BudgetApiService.swift in Sources */,
|
||||
28AC9525233C42D100BFB70A /* BudgetAppApiService.swift in Sources */,
|
||||
2888234723512DBF003D3847 /* Observable.swift in Sources */,
|
||||
2857EAED233DA30B0026BC83 /* LoadingView.swift in Sources */,
|
||||
282126A3235ABC1800072D52 /* BudgetAppInMemoryCacheService.swift in Sources */,
|
||||
28FE6AFA23441E3700D5543E /* CategoryDataStore.swift in Sources */,
|
||||
28B9E50E2346BCB2007C3909 /* RegistrationView.swift in Sources */,
|
||||
284102322342E12F00EAFA29 /* CategoryListView.swift in Sources */,
|
||||
|
@ -430,6 +445,7 @@
|
|||
289510242352AAFC00BC862B /* UserDataStore.swift in Sources */,
|
||||
28FE6B002344308600D5543E /* Transaction.swift in Sources */,
|
||||
28FE6AF823441E1D00D5543E /* Category.swift in Sources */,
|
||||
282126A1235929B800072D52 /* ProfileView.swift in Sources */,
|
||||
28AC9529233C433400BFB70A /* TransactionRepository.swift in Sources */,
|
||||
28FE6AF62342E4CC00D5543E /* BudgetRepository.swift in Sources */,
|
||||
28FE6B022344331B00D5543E /* TransactionDataStore.swift in Sources */,
|
||||
|
|
|
@ -1409,5 +1409,332 @@
|
|||
landmarkType = "7">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "ECFFF901-C549-437A-A8A3-6C7AEF033822"
|
||||
shouldBeEnabled = "No"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
filePath = "BudgetApp/Transaction/TransactionListView.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "44"
|
||||
endingLineNumber = "44"
|
||||
landmarkName = "init(_:category:)"
|
||||
landmarkType = "7">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "DB07923E-819C-4854-97A5-94A5E1790913"
|
||||
shouldBeEnabled = "No"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
filePath = "BudgetApp/Category/CategoryRepository.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "33"
|
||||
endingLineNumber = "33"
|
||||
landmarkName = "getCategories(budgetId:count:page:)"
|
||||
landmarkType = "7">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "93983EAA-5286-4CBE-928D-86988FB1D153"
|
||||
shouldBeEnabled = "No"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
filePath = "BudgetApp/Category/CategoryDataStore.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "26"
|
||||
endingLineNumber = "26"
|
||||
landmarkName = "getCategories(budgetId:count:page:)"
|
||||
landmarkType = "7">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "33A6CFC5-F78D-491F-A793-732352FDD1CB"
|
||||
shouldBeEnabled = "No"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
filePath = "BudgetApp/Category/CategoryDataStore.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "39"
|
||||
endingLineNumber = "39"
|
||||
landmarkName = "getCategories(budgetId:count:page:)"
|
||||
landmarkType = "7">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "CA398500-BEE8-4717-B727-FAA2C62EC711"
|
||||
shouldBeEnabled = "No"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
filePath = "BudgetApp/Category/CategoryRepository.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "29"
|
||||
endingLineNumber = "29"
|
||||
landmarkName = "getCategories(budgetId:count:page:)"
|
||||
landmarkType = "7">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.ExceptionBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "152C6CF4-A03F-46DB-A690-08535EE452D6"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
scope = "0"
|
||||
stopOnStyle = "0">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "C352446F-A182-4BAB-8C01-23DB336F2301"
|
||||
shouldBeEnabled = "No"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
filePath = "BudgetApp/Network/BudgetAppInMemoryCacheService.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "74"
|
||||
endingLineNumber = "74"
|
||||
landmarkName = "getCategories(budgetId:count:page:)"
|
||||
landmarkType = "7">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "C00678FC-5D4C-4072-93FD-6D2C0E4D47E9"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
filePath = "BudgetApp/User/AuthenticationDataStore.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "37"
|
||||
endingLineNumber = "37"
|
||||
landmarkName = "login(username:password:)"
|
||||
landmarkType = "7">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "0A8006D2-C24E-4AE5-8F7E-78ED5D1D6B7D"
|
||||
shouldBeEnabled = "No"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
filePath = "BudgetApp/User/AuthenticationDataStore.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "88"
|
||||
endingLineNumber = "88"
|
||||
landmarkName = "init(_:)"
|
||||
landmarkType = "7">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "F00DC3DF-E704-4B70-B744-57B4767E501D"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
filePath = "BudgetApp/User/AuthenticationDataStore.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "74"
|
||||
endingLineNumber = "74"
|
||||
landmarkName = "loadFromExistingSession()"
|
||||
landmarkType = "7">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "C0F886DF-04BE-4C45-9A4B-17370122B446"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
filePath = "BudgetApp/User/AuthenticationDataStore.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "81"
|
||||
endingLineNumber = "81"
|
||||
landmarkName = "loadFromExistingSession()"
|
||||
landmarkType = "7">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "3E49E2C2-F186-44D2-BA8C-DB27F0623DE1"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
filePath = "BudgetApp/Network/BudgetAppApiService.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "260"
|
||||
endingLineNumber = "260"
|
||||
landmarkName = "delete(_:)"
|
||||
landmarkType = "7">
|
||||
<Locations>
|
||||
<Location
|
||||
uuid = "3E49E2C2-F186-44D2-BA8C-DB27F0623DE1 - 36db867e35bc722d"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
symbolName = "closure #1 (Foundation.Data, __C.NSURLResponse) throws -> BudgetApp.Empty in BudgetApp.RequestHelper.delete(Swift.String) -> Combine.AnyPublisher<BudgetApp.Empty, BudgetApp.NetworkError>"
|
||||
moduleName = "BudgetApp"
|
||||
usesParentBreakpointCondition = "Yes"
|
||||
urlString = "file:///Users/billy/Projects/Budget/BudgetApp/Network/BudgetAppApiService.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "260"
|
||||
endingLineNumber = "260"
|
||||
offsetFromSymbolStart = "590">
|
||||
</Location>
|
||||
<Location
|
||||
uuid = "3E49E2C2-F186-44D2-BA8C-DB27F0623DE1 - 36db867e35bc722d"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
symbolName = "closure #1 (Foundation.Data, __C.NSURLResponse) throws -> BudgetApp.Empty in BudgetApp.RequestHelper.delete(Swift.String) -> Combine.AnyPublisher<BudgetApp.Empty, BudgetApp.NetworkError>"
|
||||
moduleName = "BudgetApp"
|
||||
usesParentBreakpointCondition = "Yes"
|
||||
urlString = "file:///Users/billy/Projects/Budget/BudgetApp/Network/BudgetAppApiService.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "260"
|
||||
endingLineNumber = "260"
|
||||
offsetFromSymbolStart = "871">
|
||||
</Location>
|
||||
<Location
|
||||
uuid = "3E49E2C2-F186-44D2-BA8C-DB27F0623DE1 - 36db867e35bc722d"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
symbolName = "closure #1 (Foundation.Data, __C.NSURLResponse) throws -> BudgetApp.Empty in BudgetApp.RequestHelper.delete(Swift.String) -> Combine.AnyPublisher<BudgetApp.Empty, BudgetApp.NetworkError>"
|
||||
moduleName = "BudgetApp"
|
||||
usesParentBreakpointCondition = "Yes"
|
||||
urlString = "file:///Users/billy/Projects/Budget/BudgetApp/Network/BudgetAppApiService.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "260"
|
||||
endingLineNumber = "260"
|
||||
offsetFromSymbolStart = "1042">
|
||||
</Location>
|
||||
<Location
|
||||
uuid = "3E49E2C2-F186-44D2-BA8C-DB27F0623DE1 - 36db867e35bc722d"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
symbolName = "closure #1 (Foundation.Data, __C.NSURLResponse) throws -> BudgetApp.Empty in BudgetApp.RequestHelper.delete(Swift.String) -> Combine.AnyPublisher<BudgetApp.Empty, BudgetApp.NetworkError>"
|
||||
moduleName = "BudgetApp"
|
||||
usesParentBreakpointCondition = "Yes"
|
||||
urlString = "file:///Users/billy/Projects/Budget/BudgetApp/Network/BudgetAppApiService.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "260"
|
||||
endingLineNumber = "260"
|
||||
offsetFromSymbolStart = "1118">
|
||||
</Location>
|
||||
<Location
|
||||
uuid = "3E49E2C2-F186-44D2-BA8C-DB27F0623DE1 - 36db867e35bc722d"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
symbolName = "closure #1 (Foundation.Data, __C.NSURLResponse) throws -> BudgetApp.Empty in BudgetApp.RequestHelper.delete(Swift.String) -> Combine.AnyPublisher<BudgetApp.Empty, BudgetApp.NetworkError>"
|
||||
moduleName = "BudgetApp"
|
||||
usesParentBreakpointCondition = "Yes"
|
||||
urlString = "file:///Users/billy/Projects/Budget/BudgetApp/Network/BudgetAppApiService.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "260"
|
||||
endingLineNumber = "260"
|
||||
offsetFromSymbolStart = "1269">
|
||||
</Location>
|
||||
</Locations>
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "DC8F6E88-A769-4ADC-8354-9BC089230827"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
filePath = "BudgetApp/Network/BudgetAppApiService.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "262"
|
||||
endingLineNumber = "262"
|
||||
landmarkName = "delete(_:)"
|
||||
landmarkType = "7">
|
||||
<Locations>
|
||||
<Location
|
||||
uuid = "DC8F6E88-A769-4ADC-8354-9BC089230827 - 36db867e35bc722d"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
symbolName = "closure #1 (Foundation.Data, __C.NSURLResponse) throws -> BudgetApp.Empty in BudgetApp.RequestHelper.delete(Swift.String) -> Combine.AnyPublisher<BudgetApp.Empty, BudgetApp.NetworkError>"
|
||||
moduleName = "BudgetApp"
|
||||
usesParentBreakpointCondition = "Yes"
|
||||
urlString = "file:///Users/billy/Projects/Budget/BudgetApp/Network/BudgetAppApiService.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "262"
|
||||
endingLineNumber = "262"
|
||||
offsetFromSymbolStart = "1102">
|
||||
</Location>
|
||||
<Location
|
||||
uuid = "DC8F6E88-A769-4ADC-8354-9BC089230827 - 36db867e35bc722d"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
symbolName = "closure #1 (Foundation.Data, __C.NSURLResponse) throws -> BudgetApp.Empty in BudgetApp.RequestHelper.delete(Swift.String) -> Combine.AnyPublisher<BudgetApp.Empty, BudgetApp.NetworkError>"
|
||||
moduleName = "BudgetApp"
|
||||
usesParentBreakpointCondition = "Yes"
|
||||
urlString = "file:///Users/billy/Projects/Budget/BudgetApp/Network/BudgetAppApiService.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "262"
|
||||
endingLineNumber = "262"
|
||||
offsetFromSymbolStart = "1178">
|
||||
</Location>
|
||||
<Location
|
||||
uuid = "DC8F6E88-A769-4ADC-8354-9BC089230827 - 36db867e35bc722d"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
symbolName = "closure #1 (Foundation.Data, __C.NSURLResponse) throws -> BudgetApp.Empty in BudgetApp.RequestHelper.delete(Swift.String) -> Combine.AnyPublisher<BudgetApp.Empty, BudgetApp.NetworkError>"
|
||||
moduleName = "BudgetApp"
|
||||
usesParentBreakpointCondition = "Yes"
|
||||
urlString = "file:///Users/billy/Projects/Budget/BudgetApp/Network/BudgetAppApiService.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "262"
|
||||
endingLineNumber = "262"
|
||||
offsetFromSymbolStart = "1180">
|
||||
</Location>
|
||||
</Locations>
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
</Breakpoints>
|
||||
</Bucket>
|
||||
|
|
|
@ -75,8 +75,10 @@ struct BudgetListItemView: View {
|
|||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
struct BudgetListsView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
BudgetListsView(MockDataStoreProvider())
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -19,18 +19,33 @@ protocol BudgetRepository {
|
|||
}
|
||||
|
||||
class NetworkBudgetRepository: BudgetRepository {
|
||||
let apiService: BudgetApiService
|
||||
let apiService: BudgetAppApiService
|
||||
let cacheService: BudgetAppInMemoryCacheService?
|
||||
|
||||
init(_ apiService: BudgetApiService) {
|
||||
init(_ apiService: BudgetAppApiService, cacheService: BudgetAppInMemoryCacheService? = nil) {
|
||||
self.apiService = apiService
|
||||
self.cacheService = cacheService
|
||||
}
|
||||
|
||||
func getBudgets(count: Int?, page: Int?) -> AnyPublisher<[Budget], NetworkError> {
|
||||
return apiService.getBudgets(count: count, page: page)
|
||||
if let budgets = cacheService?.getBudgets(count: count, page: page) {
|
||||
return budgets
|
||||
}
|
||||
|
||||
return apiService.getBudgets(count: count, page: page).map { (budgets: [Budget]) in
|
||||
self.cacheService?.addBudgets(budgets)
|
||||
return budgets
|
||||
}.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func getBudget(_ id: Int) -> AnyPublisher<Budget, NetworkError> {
|
||||
return apiService.getBudget(id)
|
||||
if let budget = cacheService?.getBudget(id) {
|
||||
return budget
|
||||
}
|
||||
return apiService.getBudget(id).map { budget in
|
||||
self.cacheService?.addBudget(budget)
|
||||
return budget
|
||||
}.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func getBudgetBalance(_ id: Int) -> AnyPublisher<Int, NetworkError> {
|
||||
|
@ -54,10 +69,10 @@ class NetworkBudgetRepository: BudgetRepository {
|
|||
|
||||
class MockBudgetRepository: BudgetRepository {
|
||||
static let budget = Budget(
|
||||
id: 1,
|
||||
name: "Test Budget",
|
||||
description: "A mock budget used for testing",
|
||||
users: []
|
||||
id: 1,
|
||||
name: "Test Budget",
|
||||
description: "A mock budget used for testing",
|
||||
users: []
|
||||
)
|
||||
|
||||
func getBudgets(count: Int?, page: Int?) -> AnyPublisher<[Budget], NetworkError> {
|
||||
|
@ -82,7 +97,7 @@ class MockBudgetRepository: BudgetRepository {
|
|||
name: "Test Budget",
|
||||
description: "A mock budget used for testing",
|
||||
users: []
|
||||
)).eraseToAnyPublisher()
|
||||
)).eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func deleteBudget(_ id: Int) -> AnyPublisher<Empty, NetworkError> {
|
||||
|
|
|
@ -35,6 +35,7 @@ class CategoryDataStore: ObservableObject {
|
|||
self.categories = .failure(error)
|
||||
}
|
||||
}, receiveValue: { (categories) in
|
||||
print("Received \(categories.count) categories")
|
||||
self.categories = .success(categories)
|
||||
})
|
||||
}
|
||||
|
@ -51,8 +52,8 @@ class CategoryDataStore: ObservableObject {
|
|||
case .failure(let error):
|
||||
self.category = .failure(error)
|
||||
}
|
||||
}, receiveValue: { (categories) in
|
||||
self.category = .success(categories)
|
||||
}, receiveValue: { (category) in
|
||||
self.category = .success(category)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -15,18 +15,37 @@ protocol CategoryRepository {
|
|||
}
|
||||
|
||||
class NetworkCategoryRepository: CategoryRepository {
|
||||
let apiService: BudgetApiService
|
||||
|
||||
init(_ apiService: BudgetApiService) {
|
||||
let apiService: BudgetAppApiService
|
||||
let cacheService: BudgetAppInMemoryCacheService?
|
||||
|
||||
init(_ apiService: BudgetAppApiService, cacheService: BudgetAppInMemoryCacheService? = nil) {
|
||||
self.apiService = apiService
|
||||
self.cacheService = cacheService
|
||||
}
|
||||
|
||||
func getCategories(budgetId: Int?, count: Int?, page: Int?) -> AnyPublisher<[Category], NetworkError> {
|
||||
return apiService.getCategories(budgetId: budgetId, count: count, page: page)
|
||||
if let categories = cacheService?.getCategories(budgetId: budgetId, count: count, page: page) {
|
||||
print("Returning categories from cache")
|
||||
return categories
|
||||
}
|
||||
|
||||
print("No cached categories, fetching from network")
|
||||
return apiService.getCategories(budgetId: budgetId, count: count, page: page).map { (categories: [Category]) in
|
||||
self.cacheService?.addCategories(categories)
|
||||
return categories
|
||||
}.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func getCategory(_ categoryId: Int) -> AnyPublisher<Category, NetworkError> {
|
||||
return apiService.getCategory(categoryId)
|
||||
if let category = cacheService?.getCategory(categoryId) {
|
||||
print("Returning category from cache")
|
||||
return category
|
||||
}
|
||||
print("Category with ID \(categoryId) not cached, returning from network")
|
||||
return apiService.getCategory(categoryId).map { category in
|
||||
self.cacheService?.addCategory(category)
|
||||
return category
|
||||
}.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -56,6 +56,11 @@ class DataStoreProvider {
|
|||
#if DEBUG
|
||||
|
||||
class MockDataStoreProvider: DataStoreProvider {
|
||||
|
||||
override func authenticationDataStore() -> AuthenticationDataStore {
|
||||
return MockAuthenticationDataStore(MockUserRepository())
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init(
|
||||
budgetRepository: MockBudgetRepository(),
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
import Foundation
|
||||
import Combine
|
||||
|
||||
class BudgetApiService {
|
||||
class BudgetAppApiService {
|
||||
let requestHelper: RequestHelper
|
||||
|
||||
init(_ requestHelper: RequestHelper) {
|
||||
|
@ -189,6 +189,10 @@ class BudgetApiService {
|
|||
func deleteUser(_ user: User) -> AnyPublisher<Empty, NetworkError> {
|
||||
return requestHelper.delete("/users/\(user.id!)")
|
||||
}
|
||||
|
||||
func getProfile() -> AnyPublisher<User, NetworkError> {
|
||||
return requestHelper.get("/users/me")
|
||||
}
|
||||
}
|
||||
|
||||
class RequestHelper {
|
125
BudgetApp/Network/BudgetAppInMemoryCacheService.swift
Normal file
125
BudgetApp/Network/BudgetAppInMemoryCacheService.swift
Normal file
|
@ -0,0 +1,125 @@
|
|||
//
|
||||
// BudgetApiService.swift
|
||||
// Budget
|
||||
//
|
||||
// Created by Billy Brawner on 9/25/19.
|
||||
// Copyright © 2019 William Brawner. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
class BudgetAppInMemoryCacheService {
|
||||
var budgets = Set<Budget>()
|
||||
var categories = Set<Category>()
|
||||
var transactions = Set<Transaction>()
|
||||
|
||||
// MARK: Budgets
|
||||
func getBudgets(count: Int? = nil, page: Int? = nil) -> AnyPublisher<[Budget], NetworkError>? {
|
||||
let results = budgets.sorted { (first, second) -> Bool in
|
||||
return first.name < second.name
|
||||
}
|
||||
if results.isEmpty {
|
||||
return nil
|
||||
}
|
||||
return Result.Publisher(.success(results.slice(count: count, page: page))).eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func getBudget(_ id: Int) -> AnyPublisher<Budget, NetworkError>? {
|
||||
guard let budget = budgets.first(where: { $0.id == id }) else {
|
||||
return nil
|
||||
}
|
||||
return Result.Publisher(.success(budget)).eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func getBudgetBalance(_ id: Int) -> AnyPublisher<Int, NetworkError>? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func addBudgets(_ budgets: [Budget]) {
|
||||
budgets.forEach { addBudget($0) }
|
||||
}
|
||||
|
||||
func addBudget(_ budget: Budget) {
|
||||
self.budgets.insert(budget)
|
||||
}
|
||||
|
||||
// MARK: Transactions
|
||||
func getTransactions(
|
||||
budgetIds: [Int]? = nil,
|
||||
categoryIds: [Int]? = nil,
|
||||
from: Date? = nil,
|
||||
to: Date? = nil,
|
||||
count: Int? = nil,
|
||||
page: Int? = nil
|
||||
) -> AnyPublisher<[Transaction], NetworkError>? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getTransaction(_ id: Int) -> AnyPublisher<Transaction, NetworkError>? {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MARK: Categories
|
||||
func getCategories(budgetId: Int? = nil, count: Int? = nil, page: Int? = nil) -> AnyPublisher<[Category], NetworkError>? {
|
||||
var results = categories
|
||||
if budgetId != nil {
|
||||
results = categories.filter { $0.budgetId == budgetId }
|
||||
}
|
||||
if results.isEmpty {
|
||||
return nil
|
||||
}
|
||||
let sortedResults = results.sorted { $0.title < $1.title }
|
||||
// TODO: Figure out why this crashes on transaction editing screens
|
||||
// return Result.Publisher(.success(sortedResults.slice(count: count, page: page))).eraseToAnyPublisher()
|
||||
return nil
|
||||
}
|
||||
|
||||
func getCategory(_ id: Int) -> AnyPublisher<Category, NetworkError>? {
|
||||
guard let category = categories.first(where: { $0.id == id }) else {
|
||||
return nil
|
||||
}
|
||||
return Result.Publisher(.success(category)).eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func getCategoryBalance(_ id: Int) -> AnyPublisher<Int, NetworkError>? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func addCategories(_ categories: [Category]) {
|
||||
categories.forEach { addCategory($0) }
|
||||
}
|
||||
|
||||
func addCategory(_ category: Category) {
|
||||
self.categories.insert(category)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Users
|
||||
func getUser(id: Int) -> AnyPublisher<User, NetworkError>? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getUsers(count: Int? = nil, page: Int? = nil) -> AnyPublisher<[User], NetworkError>? {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines which slice of the array should be returned based on the count and page parameters
|
||||
*/
|
||||
private func calculateStartAndEndIndices(count: Int, page: Int?) -> (start: Int, end: Int?) {
|
||||
let end = count * (page ?? 1)
|
||||
let start = max(end - count, 0)
|
||||
return (start, end)
|
||||
}
|
||||
|
||||
extension Array {
|
||||
func slice(count: Int?, page: Int?) -> Array<Element> {
|
||||
if count == nil {
|
||||
return self
|
||||
}
|
||||
let indices: (Int, Int?) = calculateStartAndEndIndices(count: count!, page: page)
|
||||
return Array(self[indices.0..<Swift.min((indices.1 ?? self.count), self.count)])
|
||||
}
|
||||
}
|
52
BudgetApp/Profile/ProfileView.swift
Normal file
52
BudgetApp/Profile/ProfileView.swift
Normal file
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// ProfileView.swift
|
||||
// BudgetApp
|
||||
//
|
||||
// Created by Billy Brawner on 10/17/19.
|
||||
// Copyright © 2019 William Brawner. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ProfileView: View {
|
||||
let currentUser: User
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
VStack(spacing: 10) {
|
||||
Image(systemName: "person.circle.fill")
|
||||
.frame(width: 100, height: 100, alignment: .center)
|
||||
.scaledToFill()
|
||||
.clipShape(Circle())
|
||||
.overlay(Circle().stroke(Color.white, lineWidth: 4))
|
||||
.shadow(radius: 5)
|
||||
Text(currentUser.username)
|
||||
NavigationLink(destination: EmptyView()) {
|
||||
Text("change_password")
|
||||
}
|
||||
NavigationLink(destination: EmptyView()) {
|
||||
Text("change_email")
|
||||
}
|
||||
NavigationLink(destination: EmptyView()) {
|
||||
Text("delete_account")
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
}
|
||||
.navigationBarTitle("profile")
|
||||
}
|
||||
}
|
||||
|
||||
let dataStoreProvider: DataStoreProvider
|
||||
init(_ dataStoreProvider: DataStoreProvider) {
|
||||
self.dataStoreProvider = dataStoreProvider
|
||||
self.currentUser = try! dataStoreProvider.authenticationDataStore().currentUser.get()
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
struct ProfileView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ProfileView(MockDataStoreProvider())
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -10,22 +10,23 @@ import UIKit
|
|||
import SwiftUI
|
||||
|
||||
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
#if DEBUG
|
||||
// Uncomment this for local development
|
||||
// static let baseUrl = "http://localhost:8080"
|
||||
static let baseUrl = "https://budget-api.intra.wbrawner.com"
|
||||
#else
|
||||
static let baseUrl = "https://budget-api.intra.wbrawner.com"
|
||||
#endif
|
||||
var window: UIWindow?
|
||||
let dataStoreProvider: DataStoreProvider
|
||||
|
||||
override init() {
|
||||
// TODO: Dependency injection?
|
||||
#if DEBUG
|
||||
// Uncomment this for local development
|
||||
// let baseUrl = "http://localhost:8080"
|
||||
let baseUrl = "https://budget-api.intra.wbrawner.com"
|
||||
#else
|
||||
let baseUrl = "https://budget-api.intra.wbrawner.com"
|
||||
#endif
|
||||
let requestHelper = RequestHelper(baseUrl)
|
||||
let apiService = BudgetApiService(requestHelper)
|
||||
let budgetRepository = NetworkBudgetRepository(apiService)
|
||||
let categoryRepository = NetworkCategoryRepository(apiService)
|
||||
let requestHelper = RequestHelper(SceneDelegate.baseUrl)
|
||||
let cacheService = BudgetAppInMemoryCacheService()
|
||||
let apiService = BudgetAppApiService(requestHelper)
|
||||
let budgetRepository = NetworkBudgetRepository(apiService, cacheService: cacheService)
|
||||
let categoryRepository = NetworkCategoryRepository(apiService, cacheService: cacheService)
|
||||
let transactionRepository = NetworkTransactionRepository(apiService)
|
||||
let userRepository = NetworkUserRepository(apiService)
|
||||
dataStoreProvider = DataStoreProvider(
|
||||
|
@ -41,7 +42,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
|
||||
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
|
||||
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
|
||||
|
||||
// Create the SwiftUI view that provides the window contents.
|
||||
let contentView = ContentView(dataStoreProvider)
|
||||
// Use a UIHostingController as window root view controller.
|
||||
|
|
|
@ -25,12 +25,9 @@ struct TabbedBudgetView: View {
|
|||
self.isAddingTransaction = true
|
||||
}) {
|
||||
Image(systemName: "plus")
|
||||
.padding()
|
||||
}
|
||||
)
|
||||
.sheet(isPresented: $isAddingTransaction, content: {
|
||||
AddTransactionView(self.dataStoreProvider)
|
||||
.navigationBarTitle("add_transaction")
|
||||
})
|
||||
}
|
||||
.tabItem {
|
||||
Image(systemName: "dollarsign.circle.fill")
|
||||
|
@ -42,17 +39,24 @@ struct TabbedBudgetView: View {
|
|||
Text("budgets")
|
||||
}
|
||||
|
||||
Text("Profile here").tabItem {
|
||||
ProfileView(dataStoreProvider).tabItem {
|
||||
Image(systemName: "person.circle.fill")
|
||||
Text("profile")
|
||||
}
|
||||
}.edgesIgnoringSafeArea(.top)
|
||||
.sheet(isPresented: $isAddingTransaction, content: {
|
||||
AddTransactionView(self.dataStoreProvider)
|
||||
.navigationBarTitle("add_transaction")
|
||||
})
|
||||
}
|
||||
|
||||
let dataStoreProvider: DataStoreProvider
|
||||
init (_ userData: AuthenticationDataStore, dataStoreProvider: DataStoreProvider) {
|
||||
self.userData = userData
|
||||
self.dataStoreProvider = dataStoreProvider
|
||||
// Warm up the caches
|
||||
self.dataStoreProvider.budgetsDataStore().getBudgets()
|
||||
self.dataStoreProvider.categoryDataStore().getCategories()
|
||||
}
|
||||
}
|
||||
//
|
||||
|
|
|
@ -90,6 +90,7 @@ struct CategoryPicker: View {
|
|||
var stateContent: AnyView {
|
||||
switch self.categoryDataStore.categories {
|
||||
case .success(let categories):
|
||||
print("Using returned categories")
|
||||
return AnyView(
|
||||
Picker("prompt_category", selection: self.categoryId) {
|
||||
ForEach(categories) { category in
|
||||
|
@ -99,9 +100,7 @@ struct CategoryPicker: View {
|
|||
)
|
||||
default:
|
||||
return AnyView(
|
||||
Picker("prompt_category", selection: self.categoryId) {
|
||||
Text("")
|
||||
}
|
||||
EmptyView()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -113,7 +112,10 @@ struct CategoryPicker: View {
|
|||
@ObservedObject var categoryDataStore: CategoryDataStore
|
||||
init(_ dataStoreProvider: DataStoreProvider, budgetId: Binding<Int?>, categoryId: Binding<Int?>) {
|
||||
let categoryDataStore = dataStoreProvider.categoryDataStore()
|
||||
categoryDataStore.getCategories(budgetId: budgetId.wrappedValue, count: nil, page: nil)
|
||||
print("Requesting categories")
|
||||
if let id = budgetId.wrappedValue {
|
||||
categoryDataStore.getCategories(budgetId: id, count: nil, page: nil)
|
||||
}
|
||||
self.categoryDataStore = categoryDataStore
|
||||
self.categoryId = categoryId
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct Transaction: Identifiable, Codable {
|
||||
struct Transaction: Identifiable, Hashable, Codable {
|
||||
let id: Int?
|
||||
let title: String
|
||||
let description: String?
|
||||
|
|
|
@ -163,8 +163,10 @@ struct UserLineItem: View {
|
|||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
struct TransactionDetailsView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
TransactionDetailsView(MockDataStoreProvider(), transactionId: 2)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -82,8 +82,10 @@ struct TransactionEditView: View {
|
|||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
struct TransactionEditView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
TransactionEditView(MockDataStoreProvider(), transaction: MockTransactionRepository.transaction, shouldNavigateUp: .constant(false))
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -44,6 +44,7 @@ struct TransactionListView: View {
|
|||
self.dataStoreProvider = dataStoreProvider
|
||||
self.transactionDataStore = dataStoreProvider.transactionDataStore()
|
||||
self.category = category
|
||||
self.transactionDataStore.getTransactions(self.category)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,9 +18,9 @@ protocol TransactionRepository {
|
|||
}
|
||||
|
||||
class NetworkTransactionRepository: TransactionRepository {
|
||||
let apiService: BudgetApiService
|
||||
let apiService: BudgetAppApiService
|
||||
|
||||
init(_ apiService: BudgetApiService) {
|
||||
init(_ apiService: BudgetAppApiService) {
|
||||
self.apiService = apiService
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,9 @@ class AuthenticationDataStore: ObservableObject {
|
|||
self.currentUser = .failure(.failedAuthentication)
|
||||
}
|
||||
}) { (user) in
|
||||
if let sessionCookie = HTTPCookieStorage.shared.cookies(for: URL(string: SceneDelegate.baseUrl)!)?.first(where: { $0.name == SESSION_KEY }) {
|
||||
UserDefaults.standard.set(sessionCookie.value, forKey: SESSION_KEY)
|
||||
}
|
||||
self.currentUser = .success(user)
|
||||
}
|
||||
}
|
||||
|
@ -62,8 +65,34 @@ class AuthenticationDataStore: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
private func loadFromExistingSession() {
|
||||
self.currentUser = .failure(.authenticating)
|
||||
|
||||
_ = self.userRepository.getProfile()
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveCompletion: { (status) in
|
||||
switch status {
|
||||
case .finished:
|
||||
return
|
||||
case .failure(_):
|
||||
self.currentUser = .failure(.unauthenticated)
|
||||
}
|
||||
}) { (user) in
|
||||
self.currentUser = .success(user)
|
||||
}
|
||||
}
|
||||
|
||||
init(_ userRepository: UserRepository) {
|
||||
self.userRepository = userRepository
|
||||
if let sessionKey = UserDefaults.standard.string(forKey: SESSION_KEY) {
|
||||
HTTPCookieStorage.shared.setCookie(HTTPCookie(properties: [
|
||||
HTTPCookiePropertyKey.name: SESSION_KEY,
|
||||
HTTPCookiePropertyKey.value: sessionKey,
|
||||
HTTPCookiePropertyKey.domain: URL(string: SceneDelegate.baseUrl)!.host!,
|
||||
HTTPCookiePropertyKey.path: "/"
|
||||
])!)
|
||||
loadFromExistingSession()
|
||||
}
|
||||
}
|
||||
|
||||
// Needed since the default implementation is currently broken
|
||||
|
@ -71,6 +100,8 @@ class AuthenticationDataStore: ObservableObject {
|
|||
private let userRepository: UserRepository
|
||||
}
|
||||
|
||||
private let SESSION_KEY = "SESSION"
|
||||
|
||||
enum UserStatus: Error, Equatable {
|
||||
case unauthenticated
|
||||
case authenticating
|
||||
|
@ -78,3 +109,12 @@ enum UserStatus: Error, Equatable {
|
|||
case authenticated
|
||||
case passwordMismatch // Passwords don't match
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
class MockAuthenticationDataStore: AuthenticationDataStore {
|
||||
override init(_ userRepository: UserRepository) {
|
||||
super.init(userRepository)
|
||||
self.currentUser = .success(User(id: 1, username: "test_user", email: "test@localhost.loc", avatar: nil))
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -14,12 +14,13 @@ protocol UserRepository {
|
|||
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>
|
||||
func getProfile() -> AnyPublisher<User, NetworkError>
|
||||
}
|
||||
|
||||
class NetworkUserRepository: UserRepository {
|
||||
let apiService: BudgetApiService
|
||||
let apiService: BudgetAppApiService
|
||||
|
||||
init(_ apiService: BudgetApiService) {
|
||||
init(_ apiService: BudgetAppApiService) {
|
||||
self.apiService = apiService
|
||||
}
|
||||
|
||||
|
@ -38,6 +39,10 @@ class NetworkUserRepository: UserRepository {
|
|||
func register(username: String, email: String, password: String) -> AnyPublisher<User, NetworkError> {
|
||||
return apiService.register(username: username, email: email, password: password)
|
||||
}
|
||||
|
||||
func getProfile() -> AnyPublisher<User, NetworkError> {
|
||||
return apiService.getProfile()
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
@ -64,6 +69,10 @@ class MockUserRepository: UserRepository {
|
|||
return Result<User, NetworkError>.Publisher(MockUserRepository.user)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func getProfile() -> AnyPublisher<User, NetworkError> {
|
||||
return Result.Publisher(MockUserRepository.user).eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in a new issue