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

View file

@ -2,6 +2,6 @@
<Workspace
version = "1.0">
<FileRef
location = "self:Budget.xcodeproj">
location = "self:/Users/billy/Projects/Budget/BudgetApp.xcodeproj">
</FileRef>
</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"
startingLineNumber = "52"
endingLineNumber = "52"
landmarkName = "createTransaction(_:)"
landmarkName = "getTransaction(_:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
@ -717,7 +717,7 @@
endingColumnNumber = "9223372036854775807"
startingLineNumber = "59"
endingLineNumber = "59"
landmarkName = "createTransaction(_:)"
landmarkName = "getTransaction(_:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
@ -733,7 +733,7 @@
endingColumnNumber = "9223372036854775807"
startingLineNumber = "47"
endingLineNumber = "47"
landmarkName = "createTransaction(_:)"
landmarkName = "getTransactions(_:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
@ -813,8 +813,8 @@
endingColumnNumber = "9223372036854775807"
startingLineNumber = "24"
endingLineNumber = "24"
landmarkName = "createTransaction(_:)"
landmarkType = "7">
landmarkName = "NetworkTransactionRepository"
landmarkType = "3">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
@ -849,5 +849,37 @@
landmarkType = "7">
</BreakpointContent>
</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>
</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
// Budget
@ -74,8 +75,8 @@ struct BudgetListItemView: View {
}
}
//struct BudgetsView_Previews: PreviewProvider {
// static var previews: some View {
// BudgetsView(budgets: [])
// }
//}
struct BudgetListsView_Previews: PreviewProvider {
static var previews: some View {
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.budgetRepository.getBudgets()
_ = self.budgetRepository.getBudgets(count: count, page: page)
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { (status) in
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 categoryRepository: CategoryRepository
private let transactionRepository: TransactionRepository
private let userRepository: UserRepository
private let _userDataStore: UserDataStore
@ -35,14 +34,30 @@ class DataStoreProvider {
return self._userDataStore
}
init(_ baseUrl: String) {
let requestHelper = RequestHelper(baseUrl)
let apiService = BudgetApiService(requestHelper)
budgetRepository = BudgetRepository(apiService)
categoryRepository = CategoryRepository(apiService)
transactionRepository = TransactionRepository(apiService)
userRepository = UserRepository(apiService)
_userDataStore = UserDataStore(userRepository)
init(
budgetRepository: BudgetRepository,
categoryRepository: CategoryRepository,
transactionRepository: TransactionRepository,
userRepository: UserRepository
) {
self.budgetRepository = budgetRepository
self.categoryRepository = categoryRepository
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)
}
// TODO: Figure out how to implement this
// func deleteBudget(_ id: Int) -> AnyPublisher<Void, NetworkError> {
// return requestHelper.delete("/budgets/\(id)")
// }
func deleteBudget(_ id: Int) -> AnyPublisher<Empty, NetworkError> {
return requestHelper.delete("/budgets/\(id)")
}
// MARK: Transactions
@ -94,10 +93,9 @@ class BudgetApiService {
return requestHelper.put("/transactions/\(transaction.id!)", data: transaction)
}
// TODO: Figure out how to implement this
// func deleteTransaction(_ id: Int) -> AnyPublisher<Void, NetworkError> {
// return requestHelper.delete("/transactions/\(id)")
// }
func deleteTransaction(_ id: Int) -> AnyPublisher<Empty, NetworkError> {
return requestHelper.delete("/transactions/\(id)")
}
// MARK: Categories
@ -131,10 +129,9 @@ class BudgetApiService {
return requestHelper.put("/categories/\(category.id!)", data: category)
}
// TODO: Figure out how to implement this
// func deleteCategory(_ id: Int) -> AnyPublisher<Void, NetworkError> {
// return requestHelper.delete("/categories/\(id)")
// }
func deleteCategory(_ id: Int) -> AnyPublisher<Empty, NetworkError> {
return requestHelper.delete("/categories/\(id)")
}
// MARK: Users
func login(username: String, password: String) -> AnyPublisher<User, NetworkError> {
@ -188,6 +185,10 @@ class BudgetApiService {
func updateUser(_ user: User) -> AnyPublisher<User, NetworkError> {
return requestHelper.put("/users/\(user.id!)", data: user)
}
func deleteUser(_ user: User) -> AnyPublisher<Empty, NetworkError> {
return requestHelper.delete("/users/\(user.id!)")
}
}
class RequestHelper {
@ -280,6 +281,8 @@ class RequestHelper {
}
}
struct Empty: Codable {}
enum NetworkError: Error, Equatable {
static func == (lhs: NetworkError, rhs: NetworkError) -> Bool {
switch (lhs, rhs) {

View file

@ -22,7 +22,19 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
#else
let baseUrl = "https://budget-api.intra.wbrawner.com"
#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()
}

View file

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

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)
var categoryIds: [Int] = []
if category != nil {
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)
.sink(receiveCompletion: { (completion) in
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) {
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 {
@Binding var isAnimating: Bool