Get live previews to work

Signed-off-by: Billy Brawner <billy@wbrawner.com>
This commit is contained in:
Billy Brawner 2019-10-12 12:03:50 -07:00
parent fb7aa8fec4
commit 5d76390e67
50 changed files with 639 additions and 253 deletions

View file

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>Budget.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
</dict>
</dict>
</plist>

View file

@ -1,22 +0,0 @@
//
// BudgetRepository.swift
// Budget
//
// Created by Billy Brawner on 9/30/19.
// Copyright © 2019 William Brawner. All rights reserved.
//
import Foundation
import Combine
class BudgetRepository {
let apiService: BudgetApiService
init(_ apiService: BudgetApiService) {
self.apiService = apiService
}
func getBudgets() -> AnyPublisher<[Budget], NetworkError> {
return apiService.getBudgets()
}
}

View file

@ -1,22 +0,0 @@
//
// CategoryRepository.swift
// Budget
//
// Created by Billy Brawner on 10/1/19.
// Copyright © 2019 William Brawner. All rights reserved.
//
import Foundation
import Combine
class CategoryRepository {
let apiService: BudgetApiService
init(_ apiService: BudgetApiService) {
self.apiService = apiService
}
func getCategories(budgetId: Int? = nil, count: Int? = nil, page: Int? = nil) -> AnyPublisher<[Category], NetworkError> {
return apiService.getCategories(budgetId: budgetId, count: count, page: page)
}
}

View file

@ -1,44 +0,0 @@
//
// TransactionDetailsView.swift
// Budget
//
// Created by Billy Brawner on 10/1/19.
// Copyright © 2019 William Brawner. All rights reserved.
//
import SwiftUI
struct TransactionDetailsView: View {
@State var title: String = ""
@State var description: String? = nil
@State var date: Date = Date()
@State var amount: Int = 0
@State var category: Category? = nil
@State var expense: Bool = true
@State var createdBy: User? = nil
@State var budget: Budget? = nil
var body: some View {
List {
TextField("prompt_title", text: self.$title)
// DatePicker(
}
}
init(_ dataStoreProvider: DataStoreProvider, transaction: Transaction) {
// self.title = transaction.title
// self.description = transaction.description
// self.date = transaction.date
// self.amount = transaction.amount
// self.category = transaction.category
// self.expense = transaction.expense
// self.createdBy = transaction.createdBy
// self.budget = transaction.budget
}
}
//struct TransactionDetailsView_Previews: PreviewProvider {
// static var previews: some View {
// TransactionDetailsView()
// }
//}

View file

@ -1,26 +0,0 @@
//
// TransactionRepository.swift
// Budget
//
// Created by Billy Brawner on 9/25/19.
// Copyright © 2019 William Brawner. All rights reserved.
//
import Foundation
import Combine
class TransactionRepository {
let apiService: BudgetApiService
init(_ apiService: BudgetApiService) {
self.apiService = apiService
}
func getTransactions(categoryIds: [Int]? = nil, from: Date? = nil, count: Int? = nil, page: Int? = nil) -> AnyPublisher<[Transaction], NetworkError> {
return apiService.getTransactions(categoryIds: categoryIds, from: from, count: count, page: page)
}
func createTransaction(_ transaction: Transaction) -> AnyPublisher<Transaction, NetworkError> {
return apiService.newTransaction(transaction)
}
}

View file

@ -1,34 +0,0 @@
//
// UserRepository.swift
// Budget
//
// Created by Billy Brawner on 9/25/19.
// Copyright © 2019 William Brawner. All rights reserved.
//
import Foundation
import Combine
class UserRepository {
let apiService: BudgetApiService
init(_ apiService: BudgetApiService) {
self.apiService = apiService
}
func getUser(id: Int) -> AnyPublisher<User, NetworkError> {
return apiService.getUser(id: id)
}
func searchUsers(withUsername: String) -> AnyPublisher<[User], NetworkError> {
return apiService.searchUsers(query: withUsername)
}
func login(username: String, password: String) -> AnyPublisher<User, NetworkError> {
return apiService.login(username: username, password: password)
}
func register(username: String, email: String, password: String) -> AnyPublisher<User, NetworkError> {
return apiService.register(username: username, email: email, password: password)
}
}

View file

@ -68,7 +68,7 @@
2857EAEC233DA30B0026BC83 /* LoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingView.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>"; }; 2888234623512DBF003D3847 /* Observable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Observable.swift; sourceTree = "<group>"; };
28A1E959235006A300CA57FE /* AddTransactionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddTransactionView.swift; sourceTree = "<group>"; }; 28A1E959235006A300CA57FE /* AddTransactionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddTransactionView.swift; sourceTree = "<group>"; };
28AC94EA233C373900BFB70A /* Budget.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Budget.app; sourceTree = BUILT_PRODUCTS_DIR; }; 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>"; }; 28AC94ED233C373900BFB70A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
28AC94EF233C373900BFB70A /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; }; 28AC94EF233C373900BFB70A /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
28AC94F1233C373900BFB70A /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; }; 28AC94F1233C373900BFB70A /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
@ -76,10 +76,10 @@
28AC94F6233C373A00BFB70A /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; }; 28AC94F6233C373A00BFB70A /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
28AC94F9233C373A00BFB70A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; }; 28AC94F9233C373A00BFB70A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
28AC94FB233C373A00BFB70A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 28AC94FB233C373A00BFB70A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
28AC9500233C373A00BFB70A /* BudgetTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BudgetTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 28AC9500233C373A00BFB70A /* BudgetAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BudgetAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
28AC9504233C373A00BFB70A /* BudgetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BudgetTests.swift; sourceTree = "<group>"; }; 28AC9504233C373A00BFB70A /* BudgetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BudgetTests.swift; sourceTree = "<group>"; };
28AC9506233C373A00BFB70A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 28AC9506233C373A00BFB70A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
28AC950B233C373A00BFB70A /* BudgetUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BudgetUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 28AC950B233C373A00BFB70A /* BudgetAppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BudgetAppUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
28AC950F233C373A00BFB70A /* BudgetUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BudgetUITests.swift; sourceTree = "<group>"; }; 28AC950F233C373A00BFB70A /* BudgetUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BudgetUITests.swift; sourceTree = "<group>"; };
28AC9511233C373A00BFB70A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 28AC9511233C373A00BFB70A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
28AC9520233C381C00BFB70A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; }; 28AC9520233C381C00BFB70A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -161,9 +161,9 @@
28AC94E1233C373900BFB70A = { 28AC94E1233C373900BFB70A = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
28AC94EC233C373900BFB70A /* Budget */, 28AC94EC233C373900BFB70A /* BudgetApp */,
28AC9503233C373A00BFB70A /* BudgetTests */, 28AC9503233C373A00BFB70A /* BudgetAppTests */,
28AC950E233C373A00BFB70A /* BudgetUITests */, 28AC950E233C373A00BFB70A /* BudgetAppUITests */,
28AC94EB233C373900BFB70A /* Products */, 28AC94EB233C373900BFB70A /* Products */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
@ -171,14 +171,14 @@
28AC94EB233C373900BFB70A /* Products */ = { 28AC94EB233C373900BFB70A /* Products */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
28AC94EA233C373900BFB70A /* Budget.app */, 28AC94EA233C373900BFB70A /* BudgetApp.app */,
28AC9500233C373A00BFB70A /* BudgetTests.xctest */, 28AC9500233C373A00BFB70A /* BudgetAppTests.xctest */,
28AC950B233C373A00BFB70A /* BudgetUITests.xctest */, 28AC950B233C373A00BFB70A /* BudgetAppUITests.xctest */,
); );
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
28AC94EC233C373900BFB70A /* Budget */ = { 28AC94EC233C373900BFB70A /* BudgetApp */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
2841022A2342D8CB00EAFA29 /* Category */, 2841022A2342D8CB00EAFA29 /* Category */,
@ -201,7 +201,7 @@
28FE6AFD234428BF00D5543E /* DataStoreProvider.swift */, 28FE6AFD234428BF00D5543E /* DataStoreProvider.swift */,
2888234623512DBF003D3847 /* Observable.swift */, 2888234623512DBF003D3847 /* Observable.swift */,
); );
path = Budget; path = BudgetApp;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
28AC94F5233C373A00BFB70A /* Preview Content */ = { 28AC94F5233C373A00BFB70A /* Preview Content */ = {
@ -212,22 +212,22 @@
path = "Preview Content"; path = "Preview Content";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
28AC9503233C373A00BFB70A /* BudgetTests */ = { 28AC9503233C373A00BFB70A /* BudgetAppTests */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
28AC9504233C373A00BFB70A /* BudgetTests.swift */, 28AC9504233C373A00BFB70A /* BudgetTests.swift */,
28AC9506233C373A00BFB70A /* Info.plist */, 28AC9506233C373A00BFB70A /* Info.plist */,
); );
path = BudgetTests; path = BudgetAppTests;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
28AC950E233C373A00BFB70A /* BudgetUITests */ = { 28AC950E233C373A00BFB70A /* BudgetAppUITests */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
28AC950F233C373A00BFB70A /* BudgetUITests.swift */, 28AC950F233C373A00BFB70A /* BudgetUITests.swift */,
28AC9511233C373A00BFB70A /* Info.plist */, 28AC9511233C373A00BFB70A /* Info.plist */,
); );
path = BudgetUITests; path = BudgetAppUITests;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
28AC9526233C42F800BFB70A /* Transaction */ = { 28AC9526233C42F800BFB70A /* Transaction */ = {
@ -264,9 +264,9 @@
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
28AC94E9233C373900BFB70A /* Budget */ = { 28AC94E9233C373900BFB70A /* BudgetApp */ = {
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 28AC9514233C373A00BFB70A /* Build configuration list for PBXNativeTarget "Budget" */; buildConfigurationList = 28AC9514233C373A00BFB70A /* Build configuration list for PBXNativeTarget "BudgetApp" */;
buildPhases = ( buildPhases = (
28AC94E6233C373900BFB70A /* Sources */, 28AC94E6233C373900BFB70A /* Sources */,
28AC94E7233C373900BFB70A /* Frameworks */, 28AC94E7233C373900BFB70A /* Frameworks */,
@ -276,14 +276,14 @@
); );
dependencies = ( dependencies = (
); );
name = Budget; name = BudgetApp;
productName = Budget; productName = Budget;
productReference = 28AC94EA233C373900BFB70A /* Budget.app */; productReference = 28AC94EA233C373900BFB70A /* BudgetApp.app */;
productType = "com.apple.product-type.application"; productType = "com.apple.product-type.application";
}; };
28AC94FF233C373A00BFB70A /* BudgetTests */ = { 28AC94FF233C373A00BFB70A /* BudgetAppTests */ = {
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 28AC9517233C373A00BFB70A /* Build configuration list for PBXNativeTarget "BudgetTests" */; buildConfigurationList = 28AC9517233C373A00BFB70A /* Build configuration list for PBXNativeTarget "BudgetAppTests" */;
buildPhases = ( buildPhases = (
28AC94FC233C373A00BFB70A /* Sources */, 28AC94FC233C373A00BFB70A /* Sources */,
28AC94FD233C373A00BFB70A /* Frameworks */, 28AC94FD233C373A00BFB70A /* Frameworks */,
@ -294,14 +294,14 @@
dependencies = ( dependencies = (
28AC9502233C373A00BFB70A /* PBXTargetDependency */, 28AC9502233C373A00BFB70A /* PBXTargetDependency */,
); );
name = BudgetTests; name = BudgetAppTests;
productName = BudgetTests; productName = BudgetTests;
productReference = 28AC9500233C373A00BFB70A /* BudgetTests.xctest */; productReference = 28AC9500233C373A00BFB70A /* BudgetAppTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test"; productType = "com.apple.product-type.bundle.unit-test";
}; };
28AC950A233C373A00BFB70A /* BudgetUITests */ = { 28AC950A233C373A00BFB70A /* BudgetAppUITests */ = {
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 28AC951A233C373A00BFB70A /* Build configuration list for PBXNativeTarget "BudgetUITests" */; buildConfigurationList = 28AC951A233C373A00BFB70A /* Build configuration list for PBXNativeTarget "BudgetAppUITests" */;
buildPhases = ( buildPhases = (
28AC9507233C373A00BFB70A /* Sources */, 28AC9507233C373A00BFB70A /* Sources */,
28AC9508233C373A00BFB70A /* Frameworks */, 28AC9508233C373A00BFB70A /* Frameworks */,
@ -312,9 +312,9 @@
dependencies = ( dependencies = (
28AC950D233C373A00BFB70A /* PBXTargetDependency */, 28AC950D233C373A00BFB70A /* PBXTargetDependency */,
); );
name = BudgetUITests; name = BudgetAppUITests;
productName = BudgetUITests; productName = BudgetUITests;
productReference = 28AC950B233C373A00BFB70A /* BudgetUITests.xctest */; productReference = 28AC950B233C373A00BFB70A /* BudgetAppUITests.xctest */;
productType = "com.apple.product-type.bundle.ui-testing"; productType = "com.apple.product-type.bundle.ui-testing";
}; };
/* End PBXNativeTarget section */ /* End PBXNativeTarget section */
@ -340,7 +340,7 @@
}; };
}; };
}; };
buildConfigurationList = 28AC94E5233C373900BFB70A /* Build configuration list for PBXProject "Budget" */; buildConfigurationList = 28AC94E5233C373900BFB70A /* Build configuration list for PBXProject "BudgetApp" */;
compatibilityVersion = "Xcode 9.3"; compatibilityVersion = "Xcode 9.3";
developmentRegion = en; developmentRegion = en;
hasScannedForEncodings = 0; hasScannedForEncodings = 0;
@ -354,9 +354,9 @@
projectDirPath = ""; projectDirPath = "";
projectRoot = ""; projectRoot = "";
targets = ( targets = (
28AC94E9233C373900BFB70A /* Budget */, 28AC94E9233C373900BFB70A /* BudgetApp */,
28AC94FF233C373A00BFB70A /* BudgetTests */, 28AC94FF233C373A00BFB70A /* BudgetAppTests */,
28AC950A233C373A00BFB70A /* BudgetUITests */, 28AC950A233C373A00BFB70A /* BudgetAppUITests */,
); );
}; };
/* End PBXProject section */ /* End PBXProject section */
@ -445,12 +445,12 @@
/* Begin PBXTargetDependency section */ /* Begin PBXTargetDependency section */
28AC9502233C373A00BFB70A /* PBXTargetDependency */ = { 28AC9502233C373A00BFB70A /* PBXTargetDependency */ = {
isa = PBXTargetDependency; isa = PBXTargetDependency;
target = 28AC94E9233C373900BFB70A /* Budget */; target = 28AC94E9233C373900BFB70A /* BudgetApp */;
targetProxy = 28AC9501233C373A00BFB70A /* PBXContainerItemProxy */; targetProxy = 28AC9501233C373A00BFB70A /* PBXContainerItemProxy */;
}; };
28AC950D233C373A00BFB70A /* PBXTargetDependency */ = { 28AC950D233C373A00BFB70A /* PBXTargetDependency */ = {
isa = PBXTargetDependency; isa = PBXTargetDependency;
target = 28AC94E9233C373900BFB70A /* Budget */; target = 28AC94E9233C373900BFB70A /* BudgetApp */;
targetProxy = 28AC950C233C373A00BFB70A /* PBXContainerItemProxy */; targetProxy = 28AC950C233C373A00BFB70A /* PBXContainerItemProxy */;
}; };
/* End PBXTargetDependency section */ /* End PBXTargetDependency section */
@ -532,6 +532,7 @@
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
PRODUCT_NAME = "";
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@ -586,6 +587,7 @@
IPHONEOS_DEPLOYMENT_TARGET = 13.0; IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_NAME = "";
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_OPTIMIZATION_LEVEL = "-O";
@ -598,10 +600,10 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_ASSET_PATHS = "\"Budget/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"BudgetApp/Preview Content\"";
DEVELOPMENT_TEAM = VJ33S6H7W7; DEVELOPMENT_TEAM = VJ33S6H7W7;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = Budget/Info.plist; INFOPLIST_FILE = BudgetApp/Info.plist;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
@ -618,10 +620,10 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_ASSET_PATHS = "\"Budget/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"BudgetApp/Preview Content\"";
DEVELOPMENT_TEAM = VJ33S6H7W7; DEVELOPMENT_TEAM = VJ33S6H7W7;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = Budget/Info.plist; INFOPLIST_FILE = BudgetApp/Info.plist;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
@ -639,7 +641,7 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
INFOPLIST_FILE = BudgetTests/Info.plist; INFOPLIST_FILE = BudgetAppTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0; IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@ -650,7 +652,7 @@
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Budget.app/Budget"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/BudgetApp.app/BudgetApp";
}; };
name = Debug; name = Debug;
}; };
@ -660,7 +662,7 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
INFOPLIST_FILE = BudgetTests/Info.plist; INFOPLIST_FILE = BudgetAppTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0; IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@ -671,7 +673,7 @@
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Budget.app/Budget"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/BudgetApp.app/BudgetApp";
}; };
name = Release; name = Release;
}; };
@ -680,7 +682,7 @@
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
INFOPLIST_FILE = BudgetUITests/Info.plist; INFOPLIST_FILE = BudgetAppUITests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
@ -699,7 +701,7 @@
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
INFOPLIST_FILE = BudgetUITests/Info.plist; INFOPLIST_FILE = BudgetAppUITests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
@ -716,7 +718,7 @@
/* End XCBuildConfiguration section */ /* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */ /* Begin XCConfigurationList section */
28AC94E5233C373900BFB70A /* Build configuration list for PBXProject "Budget" */ = { 28AC94E5233C373900BFB70A /* Build configuration list for PBXProject "BudgetApp" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
28AC9512233C373A00BFB70A /* Debug */, 28AC9512233C373A00BFB70A /* Debug */,
@ -725,7 +727,7 @@
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
28AC9514233C373A00BFB70A /* Build configuration list for PBXNativeTarget "Budget" */ = { 28AC9514233C373A00BFB70A /* Build configuration list for PBXNativeTarget "BudgetApp" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
28AC9515233C373A00BFB70A /* Debug */, 28AC9515233C373A00BFB70A /* Debug */,
@ -734,7 +736,7 @@
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
28AC9517233C373A00BFB70A /* Build configuration list for PBXNativeTarget "BudgetTests" */ = { 28AC9517233C373A00BFB70A /* Build configuration list for PBXNativeTarget "BudgetAppTests" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
28AC9518233C373A00BFB70A /* Debug */, 28AC9518233C373A00BFB70A /* Debug */,
@ -743,7 +745,7 @@
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
28AC951A233C373A00BFB70A /* Build configuration list for PBXNativeTarget "BudgetUITests" */ = { 28AC951A233C373A00BFB70A /* Build configuration list for PBXNativeTarget "BudgetAppUITests" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
28AC951B233C373A00BFB70A /* Debug */, 28AC951B233C373A00BFB70A /* Debug */,

View file

@ -2,6 +2,6 @@
<Workspace <Workspace
version = "1.0"> version = "1.0">
<FileRef <FileRef
location = "self:Budget.xcodeproj"> location = "self:/Users/billy/Projects/Budget/BudgetApp.xcodeproj">
</FileRef> </FileRef>
</Workspace> </Workspace>

View file

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1100"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "28AC94E9233C373900BFB70A"
BuildableName = "BudgetApp.app"
BlueprintName = "BudgetApp"
ReferencedContainer = "container:BudgetApp.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "28AC94FF233C373A00BFB70A"
BuildableName = "BudgetAppTests.xctest"
BlueprintName = "BudgetAppTests"
ReferencedContainer = "container:BudgetApp.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "28AC950A233C373A00BFB70A"
BuildableName = "BudgetAppUITests.xctest"
BlueprintName = "BudgetAppUITests"
ReferencedContainer = "container:BudgetApp.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "28AC94E9233C373900BFB70A"
BuildableName = "BudgetApp.app"
BlueprintName = "BudgetApp"
ReferencedContainer = "container:BudgetApp.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "28AC94E9233C373900BFB70A"
BuildableName = "BudgetApp.app"
BlueprintName = "BudgetApp"
ReferencedContainer = "container:BudgetApp.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View file

@ -701,7 +701,7 @@
endingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807"
startingLineNumber = "52" startingLineNumber = "52"
endingLineNumber = "52" endingLineNumber = "52"
landmarkName = "createTransaction(_:)" landmarkName = "getTransaction(_:)"
landmarkType = "7"> landmarkType = "7">
</BreakpointContent> </BreakpointContent>
</BreakpointProxy> </BreakpointProxy>
@ -717,7 +717,7 @@
endingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807"
startingLineNumber = "59" startingLineNumber = "59"
endingLineNumber = "59" endingLineNumber = "59"
landmarkName = "createTransaction(_:)" landmarkName = "getTransaction(_:)"
landmarkType = "7"> landmarkType = "7">
</BreakpointContent> </BreakpointContent>
</BreakpointProxy> </BreakpointProxy>
@ -733,7 +733,7 @@
endingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807"
startingLineNumber = "47" startingLineNumber = "47"
endingLineNumber = "47" endingLineNumber = "47"
landmarkName = "createTransaction(_:)" landmarkName = "getTransactions(_:)"
landmarkType = "7"> landmarkType = "7">
</BreakpointContent> </BreakpointContent>
</BreakpointProxy> </BreakpointProxy>
@ -813,8 +813,8 @@
endingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807"
startingLineNumber = "24" startingLineNumber = "24"
endingLineNumber = "24" endingLineNumber = "24"
landmarkName = "createTransaction(_:)" landmarkName = "NetworkTransactionRepository"
landmarkType = "7"> landmarkType = "3">
</BreakpointContent> </BreakpointContent>
</BreakpointProxy> </BreakpointProxy>
<BreakpointProxy <BreakpointProxy
@ -849,5 +849,37 @@
landmarkType = "7"> landmarkType = "7">
</BreakpointContent> </BreakpointContent>
</BreakpointProxy> </BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "E7F93AB9-4460-441B-9C79-F4B83BB30027"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Budget/Budget/BudgetRepository.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "26"
endingLineNumber = "26"
landmarkName = "init(_:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "E04190C6-277F-4BEF-B485-DD319C1514ED"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Budget/Budget/BudgetRepository.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "27"
endingLineNumber = "27"
landmarkName = "NetworkBudgetRepository"
landmarkType = "3">
</BreakpointContent>
</BreakpointProxy>
</Breakpoints> </Breakpoints>
</Bucket> </Bucket>

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>BudgetApp.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>
<dict>
<key>28AC94E9233C373900BFB70A</key>
<dict>
<key>primary</key>
<true/>
</dict>
<key>28AC94FF233C373A00BFB70A</key>
<dict>
<key>primary</key>
<true/>
</dict>
<key>28AC950A233C373A00BFB70A</key>
<dict>
<key>primary</key>
<true/>
</dict>
</dict>
</dict>
</plist>

View file

@ -1,3 +1,4 @@
// //
// BudgetsView.swift // BudgetsView.swift
// Budget // Budget
@ -74,8 +75,8 @@ struct BudgetListItemView: View {
} }
} }
//struct BudgetsView_Previews: PreviewProvider { struct BudgetListsView_Previews: PreviewProvider {
// static var previews: some View { static var previews: some View {
// BudgetsView(budgets: []) BudgetListsView(MockDataStoreProvider())
// } }
//} }

View file

@ -0,0 +1,101 @@
//
// BudgetRepository.swift
// Budget
//
// Created by Billy Brawner on 9/30/19.
// Copyright © 2019 William Brawner. All rights reserved.
//
import Foundation
import Combine
protocol BudgetRepository {
func getBudgets(count: Int?, page: Int?) -> AnyPublisher<[Budget], NetworkError>
func getBudget(_ id: Int) -> AnyPublisher<Budget, NetworkError>
func getBudgetBalance(_ id: Int) -> AnyPublisher<Int, NetworkError>
func newBudget(_ budget: Budget) -> AnyPublisher<Budget, NetworkError>
func updateBudget(_ budget: Budget) -> AnyPublisher<Budget, NetworkError>
func deleteBudget(_ id: Int) -> AnyPublisher<Empty, NetworkError>
}
class NetworkBudgetRepository: BudgetRepository {
let apiService: BudgetApiService
init(_ apiService: BudgetApiService) {
self.apiService = apiService
}
func getBudgets(count: Int?, page: Int?) -> AnyPublisher<[Budget], NetworkError> {
return apiService.getBudgets(count: count, page: page)
}
func getBudget(_ id: Int) -> AnyPublisher<Budget, NetworkError> {
return apiService.getBudget(id)
}
func getBudgetBalance(_ id: Int) -> AnyPublisher<Int, NetworkError> {
return apiService.getBudgetBalance(id)
}
func newBudget(_ budget: Budget) -> AnyPublisher<Budget, NetworkError> {
return apiService.newBudget(budget)
}
func updateBudget(_ budget: Budget) -> AnyPublisher<Budget, NetworkError> {
return apiService.updateBudget(budget)
}
func deleteBudget(_ id: Int) -> AnyPublisher<Empty, NetworkError> {
return apiService.deleteBudget(id)
}
}
#if DEBUG
class MockBudgetRepository: BudgetRepository {
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()
}
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()
}
func getBudgetBalance(_ id: Int) -> AnyPublisher<Int, NetworkError> {
return Result.Publisher(10000).eraseToAnyPublisher()
}
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()
}
func updateBudget(_ budget: Budget) -> AnyPublisher<Budget, NetworkError> {
return Result.Publisher(Budget(
id: 1,
name: "Test Budget",
description: "A mock budget used for testing",
users: []
)).eraseToAnyPublisher()
}
func deleteBudget(_ id: Int) -> AnyPublisher<Empty, NetworkError> {
return Result.Publisher(Empty()).eraseToAnyPublisher()
}
}
#endif

View file

@ -21,10 +21,10 @@ class BudgetsDataStore: ObservableObject {
} }
} }
func getBudgets() { func getBudgets(count: Int? = nil, page: Int? = nil) {
self.budgets = .failure(.loading) self.budgets = .failure(.loading)
_ = self.budgetRepository.getBudgets() _ = self.budgetRepository.getBudgets(count: count, page: page)
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink(receiveCompletion: { (status) in .sink(receiveCompletion: { (status) in
switch status { switch status {

View file

@ -0,0 +1,36 @@
//
// CategoryRepository.swift
// Budget
//
// Created by Billy Brawner on 10/1/19.
// Copyright © 2019 William Brawner. All rights reserved.
//
import Foundation
import Combine
protocol CategoryRepository {
func getCategories(budgetId: Int?, count: Int?, page: Int?) -> AnyPublisher<[Category], NetworkError>
}
class NetworkCategoryRepository: CategoryRepository {
let apiService: BudgetApiService
init(_ apiService: BudgetApiService) {
self.apiService = apiService
}
func getCategories(budgetId: Int?, count: Int?, page: Int?) -> AnyPublisher<[Category], NetworkError> {
return apiService.getCategories(budgetId: budgetId, count: count, page: page)
}
}
#if DEBUG
class MockCategoryRepository: CategoryRepository {
func getCategories(budgetId: Int?, count: Int?, page: Int?) -> AnyPublisher<[Category], NetworkError> {
return Result.Publisher([]).eraseToAnyPublisher()
}
}
#endif

View file

@ -15,7 +15,6 @@ class DataStoreProvider {
private let budgetRepository: BudgetRepository private let budgetRepository: BudgetRepository
private let categoryRepository: CategoryRepository private let categoryRepository: CategoryRepository
private let transactionRepository: TransactionRepository private let transactionRepository: TransactionRepository
private let userRepository: UserRepository
private let _userDataStore: UserDataStore private let _userDataStore: UserDataStore
@ -35,14 +34,30 @@ class DataStoreProvider {
return self._userDataStore return self._userDataStore
} }
init(_ baseUrl: String) { init(
let requestHelper = RequestHelper(baseUrl) budgetRepository: BudgetRepository,
let apiService = BudgetApiService(requestHelper) categoryRepository: CategoryRepository,
budgetRepository = BudgetRepository(apiService) transactionRepository: TransactionRepository,
categoryRepository = CategoryRepository(apiService) userRepository: UserRepository
transactionRepository = TransactionRepository(apiService) ) {
userRepository = UserRepository(apiService) self.budgetRepository = budgetRepository
self.categoryRepository = categoryRepository
_userDataStore = UserDataStore(userRepository) self.transactionRepository = transactionRepository
self._userDataStore = UserDataStore(userRepository)
} }
} }
#if DEBUG
class MockDataStoreProvider: DataStoreProvider {
init() {
super.init(
budgetRepository: MockBudgetRepository(),
categoryRepository: MockCategoryRepository(),
transactionRepository: MockTransactionRepository(),
userRepository: MockUserRepository()
)
}
}
#endif

View file

@ -45,10 +45,9 @@ class BudgetApiService {
return requestHelper.put("/budgets/\(budget.id!)", data: budget) return requestHelper.put("/budgets/\(budget.id!)", data: budget)
} }
// TODO: Figure out how to implement this func deleteBudget(_ id: Int) -> AnyPublisher<Empty, NetworkError> {
// func deleteBudget(_ id: Int) -> AnyPublisher<Void, NetworkError> { return requestHelper.delete("/budgets/\(id)")
// return requestHelper.delete("/budgets/\(id)") }
// }
// MARK: Transactions // MARK: Transactions
@ -94,10 +93,9 @@ class BudgetApiService {
return requestHelper.put("/transactions/\(transaction.id!)", data: transaction) return requestHelper.put("/transactions/\(transaction.id!)", data: transaction)
} }
// TODO: Figure out how to implement this func deleteTransaction(_ id: Int) -> AnyPublisher<Empty, NetworkError> {
// func deleteTransaction(_ id: Int) -> AnyPublisher<Void, NetworkError> { return requestHelper.delete("/transactions/\(id)")
// return requestHelper.delete("/transactions/\(id)") }
// }
// MARK: Categories // MARK: Categories
@ -131,10 +129,9 @@ class BudgetApiService {
return requestHelper.put("/categories/\(category.id!)", data: category) return requestHelper.put("/categories/\(category.id!)", data: category)
} }
// TODO: Figure out how to implement this func deleteCategory(_ id: Int) -> AnyPublisher<Empty, NetworkError> {
// func deleteCategory(_ id: Int) -> AnyPublisher<Void, NetworkError> { return requestHelper.delete("/categories/\(id)")
// return requestHelper.delete("/categories/\(id)") }
// }
// MARK: Users // MARK: Users
func login(username: String, password: String) -> AnyPublisher<User, NetworkError> { func login(username: String, password: String) -> AnyPublisher<User, NetworkError> {
@ -188,6 +185,10 @@ class BudgetApiService {
func updateUser(_ user: User) -> AnyPublisher<User, NetworkError> { func updateUser(_ user: User) -> AnyPublisher<User, NetworkError> {
return requestHelper.put("/users/\(user.id!)", data: user) return requestHelper.put("/users/\(user.id!)", data: user)
} }
func deleteUser(_ user: User) -> AnyPublisher<Empty, NetworkError> {
return requestHelper.delete("/users/\(user.id!)")
}
} }
class RequestHelper { class RequestHelper {
@ -280,6 +281,8 @@ class RequestHelper {
} }
} }
struct Empty: Codable {}
enum NetworkError: Error, Equatable { enum NetworkError: Error, Equatable {
static func == (lhs: NetworkError, rhs: NetworkError) -> Bool { static func == (lhs: NetworkError, rhs: NetworkError) -> Bool {
switch (lhs, rhs) { switch (lhs, rhs) {

View file

@ -22,7 +22,19 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
#else #else
let baseUrl = "https://budget-api.intra.wbrawner.com" let baseUrl = "https://budget-api.intra.wbrawner.com"
#endif #endif
dataStoreProvider = DataStoreProvider(baseUrl) let requestHelper = RequestHelper(baseUrl)
let apiService = BudgetApiService(requestHelper)
let budgetRepository = MockBudgetRepository()
// let budgetRepository = NetworkBudgetRepository(apiService)
let categoryRepository = NetworkCategoryRepository(apiService)
let transactionRepository = NetworkTransactionRepository(apiService)
let userRepository = NetworkUserRepository(apiService)
dataStoreProvider = DataStoreProvider(
budgetRepository: budgetRepository,
categoryRepository: categoryRepository,
transactionRepository: transactionRepository,
userRepository: userRepository
)
super.init() super.init()
} }

View file

@ -29,9 +29,7 @@ struct AddTransactionView: View {
return AnyView(EmptyView()) return AnyView(EmptyView())
case .failure(.loading): case .failure(.loading):
return AnyView(VStack { return AnyView(EmbeddedLoadingView())
ActivityIndicator(isAnimating: .constant(true), style: .large)
})
default: default:
// TODO: Handle each network failure type // TODO: Handle each network failure type
return AnyView(Form { return AnyView(Form {
@ -66,9 +64,7 @@ struct AddTransactionView: View {
}) })
) )
default: default:
return AnyView(VStack { return AnyView(EmbeddedLoadingView())
ActivityIndicator(isAnimating: .constant(true), style: .large)
})
} }
} }

View file

@ -22,14 +22,19 @@ class TransactionDataStore: ObservableObject {
} }
} }
func getTransactions(_ category: Category? = nil) { func getTransactions(_ category: Category? = nil, from: Date? = nil, count: Int? = nil, page: Int? = nil) {
self.transactions = .failure(.loading) self.transactions = .failure(.loading)
var categoryIds: [Int] = [] var categoryIds: [Int] = []
if category != nil { if category != nil {
categoryIds.append(category!.id!) categoryIds.append(category!.id!)
} }
_ = self.transactionRepository.getTransactions(categoryIds: categoryIds, from: Date(timeIntervalSince1970: 0)) _ = self.transactionRepository.getTransactions(
categoryIds: categoryIds,
from: Date(timeIntervalSince1970: 0),
count: count,
page: page
)
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink(receiveCompletion: { (completion) in .sink(receiveCompletion: { (completion) in
switch completion { switch completion {
@ -43,6 +48,23 @@ class TransactionDataStore: ObservableObject {
}) })
} }
func getTransaction(_ transactionId: Int) {
self.transaction = .failure(.loading)
_ = self.transactionRepository.getTransaction(transactionId)
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { (completion) in
switch completion {
case .finished:
return
case .failure(let error):
self.transaction = .failure(error)
}
}, receiveValue: { (transaction) in
self.transaction = .success(transaction)
})
}
func createTransaction(_ transaction: Transaction) { func createTransaction(_ transaction: Transaction) {
self.transaction = .failure(.loading) self.transaction = .failure(.loading)

View file

@ -0,0 +1,41 @@
//
// TransactionDetailsView.swift
// Budget
//
// Created by Billy Brawner on 10/1/19.
// Copyright © 2019 William Brawner. All rights reserved.
//
import SwiftUI
struct TransactionDetailsView: View {
var body: some View {
stateContent
}
var stateContent: AnyView {
switch transactionDataStore.transaction {
case .success(let transaction):
return AnyView(VStack {
Text(transaction.title)
})
case .failure(.loading):
return AnyView(EmbeddedLoadingView())
default:
return AnyView(Text("transaction_details_error"))
}
}
let transactionDataStore: TransactionDataStore
init(_ dataStoreProvider: DataStoreProvider, transaction: Transaction) {
let transactionDataStore = dataStoreProvider.transactionDataStore()
transactionDataStore.getTransactions()
self.transactionDataStore = transactionDataStore
}
}
//struct TransactionDetailsView_Previews: PreviewProvider {
// static var previews: some View {
// TransactionDetailsView()
// }
//}

View file

@ -0,0 +1,82 @@
//
// TransactionRepository.swift
// Budget
//
// Created by Billy Brawner on 9/25/19.
// Copyright © 2019 William Brawner. All rights reserved.
//
import Foundation
import Combine
protocol TransactionRepository {
func getTransactions(categoryIds: [Int]?, from: Date?, count: Int?, page: Int?) -> AnyPublisher<[Transaction], NetworkError>
func getTransaction(_ transactionId: Int) -> AnyPublisher<Transaction, NetworkError>
func createTransaction(_ transaction: Transaction) -> AnyPublisher<Transaction, NetworkError>
}
class NetworkTransactionRepository: TransactionRepository {
let apiService: BudgetApiService
init(_ apiService: BudgetApiService) {
self.apiService = apiService
}
func getTransactions(categoryIds: [Int]?, from: Date?, count: Int?, page: Int?) -> AnyPublisher<[Transaction], NetworkError> {
return apiService.getTransactions(categoryIds: categoryIds, from: from, count: count, page: page)
}
func getTransaction(_ transactionId: Int) -> AnyPublisher<Transaction, NetworkError> {
return apiService.getTransaction(transactionId)
}
func createTransaction(_ transaction: Transaction) -> AnyPublisher<Transaction, NetworkError> {
return apiService.newTransaction(transaction)
}
}
#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()
}
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()
}
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()
}
}
#endif

View file

@ -0,0 +1,67 @@
//
// UserRepository.swift
// Budget
//
// Created by Billy Brawner on 9/25/19.
// Copyright © 2019 William Brawner. All rights reserved.
//
import Foundation
import Combine
protocol UserRepository {
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>
}
class NetworkUserRepository: UserRepository {
let apiService: BudgetApiService
init(_ apiService: BudgetApiService) {
self.apiService = apiService
}
func getUser(id: Int) -> AnyPublisher<User, NetworkError> {
return apiService.getUser(id: id)
}
func searchUsers(withUsername: String) -> AnyPublisher<[User], NetworkError> {
return apiService.searchUsers(query: withUsername)
}
func login(username: String, password: String) -> AnyPublisher<User, NetworkError> {
return apiService.login(username: username, password: password)
}
func register(username: String, email: String, password: String) -> AnyPublisher<User, NetworkError> {
return apiService.register(username: username, email: email, password: password)
}
}
#if DEBUG
class MockUserRepository: UserRepository {
func getUser(id: Int) -> AnyPublisher<User, NetworkError> {
return Result<User, NetworkError>.Publisher(.failure(NetworkError.unknown))
.eraseToAnyPublisher()
}
func searchUsers(withUsername: String) -> AnyPublisher<[User], NetworkError> {
return Result<[User], NetworkError>.Publisher(.failure(NetworkError.unknown))
.eraseToAnyPublisher()
}
func login(username: String, password: String) -> AnyPublisher<User, NetworkError> {
return Result<User, NetworkError>.Publisher(.failure(NetworkError.unknown))
.eraseToAnyPublisher()
}
func register(username: String, email: String, password: String) -> AnyPublisher<User, NetworkError> {
return Result<User, NetworkError>.Publisher(.failure(NetworkError.unknown))
.eraseToAnyPublisher()
}
}
#endif

View file

@ -36,6 +36,14 @@ struct LoadingView<Content>: View where Content: View {
} }
} }
struct EmbeddedLoadingView: View {
var body: some View {
VStack {
ActivityIndicator(isAnimating: .constant(true), style: .large)
}
}
}
struct ActivityIndicator: UIViewRepresentable { struct ActivityIndicator: UIViewRepresentable {
@Binding var isAnimating: Bool @Binding var isAnimating: Bool