Implement basic support for multi-window

This commit is contained in:
William Brawner 2021-12-09 21:35:56 -07:00
parent b035b027ab
commit e8c7b7e3a9
14 changed files with 120 additions and 363 deletions

View file

@ -39,7 +39,6 @@
28FE6AF823441E1D00D5543E /* Category.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FE6AF723441E1D00D5543E /* Category.swift */; };
28FE6AFA23441E3700D5543E /* CategoryDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FE6AF923441E3700D5543E /* CategoryDataStore.swift */; };
28FE6AFC23441E4500D5543E /* CategoryRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FE6AFB23441E4500D5543E /* CategoryRepository.swift */; };
28FE6AFE234428BF00D5543E /* DataStoreProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FE6AFD234428BF00D5543E /* DataStoreProvider.swift */; };
28FE6B002344308600D5543E /* Transaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FE6AFF2344308600D5543E /* Transaction.swift */; };
28FE6B022344331B00D5543E /* TransactionDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FE6B012344331B00D5543E /* TransactionDataStore.swift */; };
28FE6B04234449DC00D5543E /* TransactionListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FE6B03234449DC00D5543E /* TransactionListView.swift */; };
@ -114,7 +113,6 @@
28FE6AF723441E1D00D5543E /* Category.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Category.swift; sourceTree = "<group>"; };
28FE6AF923441E3700D5543E /* CategoryDataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryDataStore.swift; sourceTree = "<group>"; };
28FE6AFB23441E4500D5543E /* CategoryRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryRepository.swift; sourceTree = "<group>"; };
28FE6AFD234428BF00D5543E /* DataStoreProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStoreProvider.swift; sourceTree = "<group>"; };
28FE6AFF2344308600D5543E /* Transaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transaction.swift; sourceTree = "<group>"; };
28FE6B012344331B00D5543E /* TransactionDataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionDataStore.swift; sourceTree = "<group>"; };
28FE6B03234449DC00D5543E /* TransactionListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionListView.swift; sourceTree = "<group>"; };
@ -225,7 +223,6 @@
children = (
282126A4235BCB7500072D52 /* Twigs.entitlements */,
28AC94FB233C373A00BFB70A /* Info.plist */,
28FE6AFD234428BF00D5543E /* DataStoreProvider.swift */,
28CE8B9423525F990072BC4C /* Extensions.swift */,
28AC94F1233C373900BFB70A /* LoginView.swift */,
2888234623512DBF003D3847 /* Observable.swift */,
@ -461,7 +458,6 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
28FE6AFE234428BF00D5543E /* DataStoreProvider.swift in Sources */,
28A1E95A235006A300CA57FE /* AddTransactionView.swift in Sources */,
2821266023555FD300072D52 /* EditTransactionForm.swift in Sources */,
282126622357E45F00072D52 /* TransactionEditView.swift in Sources */,

View file

@ -17,51 +17,7 @@ protocol BudgetRepository {
func deleteBudget(_ id: String) -> AnyPublisher<Empty, NetworkError>
}
class NetworkBudgetRepository: BudgetRepository {
let apiService: TwigsApiService
let cacheService: TwigsInMemoryCacheService?
init(_ apiService: TwigsApiService, cacheService: TwigsInMemoryCacheService? = nil) {
self.apiService = apiService
self.cacheService = cacheService
}
func getBudgets(count: Int?, page: Int?) -> AnyPublisher<[Budget], NetworkError> {
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: String) -> AnyPublisher<Budget, NetworkError> {
if let budget = cacheService?.getBudget(id) {
return budget
}
return apiService.getBudget(id).map { budget in
self.cacheService?.addBudget(budget)
return budget
}.eraseToAnyPublisher()
}
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: String) -> AnyPublisher<Empty, NetworkError> {
return apiService.deleteBudget(id)
}
}
#if DEBUG
class MockBudgetRepository: BudgetRepository {
static let budget = Budget(
id: "1",
@ -95,5 +51,4 @@ class MockBudgetRepository: BudgetRepository {
return Result.Publisher(Empty()).eraseToAnyPublisher()
}
}
#endif

View file

@ -31,6 +31,7 @@ class BudgetsDataStore: ObservableObject {
self.budgetRepository = budgetRepository
self.categoryRepository = categoryRepository
self.transactionRepository = transactionRepository
self.getBudgets(count: nil, page: nil)
}
func getBudgets(count: Int? = nil, page: Int? = nil) {

View file

@ -17,64 +17,7 @@ protocol CategoryRepository {
func deleteCategory(_ id: String) -> AnyPublisher<Empty, NetworkError>
}
class NetworkCategoryRepository: CategoryRepository {
let apiService: TwigsApiService
let cacheService: TwigsInMemoryCacheService?
init(_ apiService: TwigsApiService, cacheService: TwigsInMemoryCacheService? = nil) {
self.apiService = apiService
self.cacheService = cacheService
}
func getCategories(budgetId: String?, expense: Bool?, archived: Bool?, count: Int?, page: Int?) -> AnyPublisher<[Category], NetworkError> {
if let categories = cacheService?.getCategories(budgetId: budgetId, expense: expense, archived: archived, count: count, page: page) {
print("Returning categories from cache")
return categories
}
print("No cached categories, fetching from network")
return apiService.getCategories(budgetId: budgetId, expense: expense, archived: archived, count: count, page: page).map { (categories: [Category]) in
self.cacheService?.addCategories(categories)
return categories
}.eraseToAnyPublisher()
}
func getCategory(_ categoryId: String) -> AnyPublisher<Category, NetworkError> {
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()
}
func createCategory(_ category: Category) -> AnyPublisher<Category, NetworkError> {
return apiService.newCategory(category).map {
self.cacheService?.addCategory($0)
return $0
}.eraseToAnyPublisher()
}
func updateCategory(_ category: Category) -> AnyPublisher<Category, NetworkError> {
return apiService.updateCategory(category).map {
self.cacheService?.updateCategory($0)
return $0
}.eraseToAnyPublisher()
}
func deleteCategory(_ id: String) -> AnyPublisher<Empty, NetworkError> {
return apiService.deleteCategory(id).map {
self.cacheService?.removeCategory(id)
return $0
}.eraseToAnyPublisher()
}
}
#if DEBUG
class MockCategoryRepository: CategoryRepository {
static let category = Category(
budgetId: MockBudgetRepository.budget.id,

View file

@ -1,74 +0,0 @@
//
// DataStoreProvider.swift
// Budget
//
// Created by Billy Brawner on 10/1/19.
// Copyright © 2019 William Brawner. All rights reserved.
//
import Foundation
/**
Wrapper for all types of data stores. Some are considered singletons, such as the AuthenticationDataStore, while others are created as needed
*/
class DataStoreProvider {
private let budgetRepository: BudgetRepository
private let categoryRepository: CategoryRepository
private let transactionRepository: TransactionRepository
private let userRepository: UserRepository
private let _authenticationDataStore: AuthenticationDataStore
func budgetsDataStore() -> BudgetsDataStore {
return BudgetsDataStore(budgetRepository: budgetRepository, categoryRepository: categoryRepository, transactionRepository: transactionRepository)
}
func categoryDataStore() -> CategoryDataStore {
return CategoryDataStore(categoryRepository)
}
func transactionDataStore() -> TransactionDataStore {
return TransactionDataStore(transactionRepository)
}
func authenticationDataStore() -> AuthenticationDataStore {
return self._authenticationDataStore
}
func userDataStore() -> UserDataStore {
return UserDataStore(userRepository)
}
init(
budgetRepository: BudgetRepository,
categoryRepository: CategoryRepository,
transactionRepository: TransactionRepository,
userRepository: UserRepository
) {
self.budgetRepository = budgetRepository
self.categoryRepository = categoryRepository
self.transactionRepository = transactionRepository
self.userRepository = userRepository
self._authenticationDataStore = AuthenticationDataStore(userRepository)
}
}
#if DEBUG
class MockDataStoreProvider: DataStoreProvider {
override func authenticationDataStore() -> AuthenticationDataStore {
return MockAuthenticationDataStore(MockUserRepository())
}
init() {
super.init(
budgetRepository: MockBudgetRepository(),
categoryRepository: MockCategoryRepository(),
transactionRepository: MockTransactionRepository(),
userRepository: MockUserRepository()
)
}
}
#endif

View file

@ -9,13 +9,29 @@
import Foundation
import Combine
class TwigsApiService: RecurringTransactionsRepository {
class TwigsApiService: BudgetRepository, CategoryRepository, RecurringTransactionsRepository, TransactionRepository, UserRepository {
let requestHelper: RequestHelper
convenience init() {
self.init(RequestHelper())
}
init(_ requestHelper: RequestHelper) {
self.requestHelper = requestHelper
}
func setToken(_ token: String) {
requestHelper.token = token
}
func setServer(_ server: String) {
var correctServer = server
if !server.starts(with: "http://") && !server.starts(with: "https://") {
correctServer = "http://\(correctServer)"
}
requestHelper.baseUrl = correctServer
}
// MARK: Budgets
func getBudgets(count: Int? = nil, page: Int? = nil) -> AnyPublisher<[Budget], NetworkError> {
@ -47,8 +63,8 @@ class TwigsApiService: RecurringTransactionsRepository {
// MARK: Transactions
func getTransactions(
budgetIds: [String]? = nil,
func getTransactions(
budgetIds: [String],
categoryIds: [String]? = nil,
from: Date? = nil,
to: Date? = nil,
@ -56,9 +72,7 @@ class TwigsApiService: RecurringTransactionsRepository {
page: Int? = nil
) -> AnyPublisher<[Transaction], NetworkError> {
var queries = [String: Array<String>]()
if budgetIds != nil {
queries["budgetIds"] = budgetIds!
}
queries["budgetIds"] = budgetIds
if categoryIds != nil {
queries["categoryIds"] = categoryIds!
}
@ -81,7 +95,7 @@ class TwigsApiService: RecurringTransactionsRepository {
return requestHelper.get("/api/transactions/\(id)")
}
func newTransaction(_ transaction: Transaction) -> AnyPublisher<Transaction, NetworkError> {
func createTransaction(_ transaction: Transaction) -> AnyPublisher<Transaction, NetworkError> {
return requestHelper.post("/api/transactions", data: transaction, type: Transaction.self)
}
@ -140,7 +154,7 @@ class TwigsApiService: RecurringTransactionsRepository {
return requestHelper.get("/api/categories/\(id)/balance")
}
func newCategory(_ category: Category) -> AnyPublisher<Category, NetworkError> {
func createCategory(_ category: Category) -> AnyPublisher<Category, NetworkError> {
return requestHelper.post("/api/categories", data: category, type: Category.self)
}
@ -174,11 +188,11 @@ class TwigsApiService: RecurringTransactionsRepository {
}.eraseToAnyPublisher()
}
func getUser(id: String) -> AnyPublisher<User, NetworkError> {
func getUser(_ id: String) -> AnyPublisher<User, NetworkError> {
return requestHelper.get("/api/users/\(id)")
}
func searchUsers(query: String) -> AnyPublisher<[User], NetworkError> {
func searchUsers(_ query: String) -> AnyPublisher<[User], NetworkError> {
return requestHelper.get(
"/api/users/search",
queries: ["query": [query]]

View file

@ -9,33 +9,35 @@
import Foundation
import Combine
class TwigsInMemoryCacheService {
class TwigsInMemoryCacheService: TwigsApiService {
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>? {
override 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 super.getBudgets(count: count, page: page).map { (budgets: [Budget]) in
self.addBudgets(budgets)
return budgets
}.eraseToAnyPublisher()
}
return Result.Publisher(.success(results.slice(count: count, page: page))).eraseToAnyPublisher()
}
func getBudget(_ id: String) -> AnyPublisher<Budget, NetworkError>? {
override func getBudget(_ id: String) -> AnyPublisher<Budget, NetworkError> {
guard let budget = budgets.first(where: { $0.id == id }) else {
return nil
return super.getBudget(id).map { budget in
self.addBudget(budget)
return budget
}.eraseToAnyPublisher()
}
return Result.Publisher(.success(budget)).eraseToAnyPublisher()
}
func getBudgetBalance(_ id: String) -> AnyPublisher<Int, NetworkError>? {
return nil
}
func addBudgets(_ budgets: [Budget]) {
budgets.forEach { addBudget($0) }
}
@ -44,24 +46,8 @@ class TwigsInMemoryCacheService {
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: String) -> AnyPublisher<Transaction, NetworkError>? {
return nil
}
// MARK: Categories
func getCategories(budgetId: String? = nil, expense: Bool? = nil, archived: Bool? = nil, count: Int? = nil, page: Int? = nil) -> AnyPublisher<[Category], NetworkError>? {
override func getCategories(budgetId: String? = nil, expense: Bool? = nil, archived: Bool? = nil, count: Int? = nil, page: Int? = nil) -> AnyPublisher<[Category], NetworkError> {
var results = categories
if budgetId != nil {
results = categories.filter { $0.budgetId == budgetId }
@ -73,23 +59,25 @@ class TwigsInMemoryCacheService {
results = results.filter { $0.archived == archived }
}
if results.isEmpty {
return nil
return super.getCategories(budgetId: budgetId, expense: expense, archived: archived, count: count, page: page).map { (categories: [Category]) in
self.addCategories(categories)
return categories
}.eraseToAnyPublisher()
}
let sortedResults = results.sorted { $0.title < $1.title }
return Result.Publisher(.success(sortedResults.slice(count: count, page: page))).eraseToAnyPublisher()
}
func getCategory(_ id: String) -> AnyPublisher<Category, NetworkError>? {
override func getCategory(_ id: String) -> AnyPublisher<Category, NetworkError> {
guard let category = categories.first(where: { $0.id == id }) else {
return nil
return super.getCategory(id).map { category in
self.addCategory(category)
return category
}.eraseToAnyPublisher()
}
return Result.Publisher(.success(category)).eraseToAnyPublisher()
}
func getCategoryBalance(_ id: String) -> AnyPublisher<Int, NetworkError>? {
return nil
}
func addCategories(_ categories: [Category]) {
categories.forEach { addCategory($0) }
}
@ -98,11 +86,26 @@ class TwigsInMemoryCacheService {
self.categories.insert(category)
}
func updateCategory(_ category: Category) {
if let index = self.categories.firstIndex(where: { $0.id == category.id }) {
self.categories.remove(at: index)
}
self.categories.insert(category)
override func createCategory(_ category: Category) -> AnyPublisher<Category, NetworkError> {
return super.createCategory(category).map {
self.categories.insert(category)
return $0
}.eraseToAnyPublisher()
}
override func updateCategory(_ category: Category) -> AnyPublisher<Category, NetworkError> {
return super.updateCategory(category).map {
self.removeCategory(category.id)
self.categories.insert(category)
return $0
}.eraseToAnyPublisher()
}
override func deleteCategory(_ id: String) -> AnyPublisher<Empty, NetworkError> {
return super.deleteCategory(id).map {
self.removeCategory(id)
return $0
}.eraseToAnyPublisher()
}
func removeCategory(_ id: String) {
@ -110,15 +113,6 @@ class TwigsInMemoryCacheService {
self.categories.remove(at: index)
}
}
// MARK: Users
func getUser(id: String) -> AnyPublisher<User, NetworkError>? {
return nil
}
func getUsers(count: Int? = nil, page: Int? = nil) -> AnyPublisher<[User], NetworkError>? {
return nil
}
}
/**

View file

@ -10,14 +10,18 @@ import SwiftUI
struct SidebarBudgetView: View {
@EnvironmentObject var authenticationDataStore: AuthenticationDataStore
@EnvironmentObject var budgetDataStore: BudgetsDataStore
@EnvironmentObject var categoryDataStore: CategoryDataStore
@StateObject var budgetDataStore: BudgetsDataStore
let apiService: TwigsApiService
@State var isSelectingBudget = true
@State var hasSelectedBudget = false
@State var isAddingTransaction = false
@State var tabSelection: Int? = 0
init(_ apiService: TwigsApiService) {
self.apiService = apiService
self._budgetDataStore = StateObject(wrappedValue: BudgetsDataStore(budgetRepository: apiService, categoryRepository: apiService, transactionRepository: apiService))
}
@ViewBuilder
var mainView: some View {
if case let .success(budget) = budgetDataStore.budget {
@ -26,32 +30,36 @@ struct SidebarBudgetView: View {
NavigationLink(
tag: 0,
selection: $tabSelection,
destination: { BudgetDetailsView(budget: budget).navigationBarTitle("overview") },
label: { Label("overview", systemImage: "chart.line.uptrend.xyaxis.circle.fill") }
destination: { BudgetDetailsView(budget: budget).navigationBarTitle("overview")
},
label: { Label("overview", systemImage: "chart.line.uptrend.xyaxis") }
)
.keyboardShortcut("1")
.keyboardShortcut("1")
NavigationLink(
tag: 1,
selection: $tabSelection,
destination: { TransactionListView(budget).navigationBarTitle("transactions") },
label: { Label("transactions", systemImage: "dollarsign.circle.fill") })
.keyboardShortcut("2")
label: { Label("transactions", systemImage: "dollarsign.circle") })
.keyboardShortcut("2")
NavigationLink(
tag: 2,
selection: $tabSelection,
destination: { CategoryListView(budget).navigationBarTitle("categories") },
label: { Label("categories", systemImage: "chart.pie.fill") })
.keyboardShortcut("3")
label: { Label("categories", systemImage: "chart.pie") })
.keyboardShortcut("3")
NavigationLink(
tag: 3,
selection: $tabSelection,
destination: { RecurringTransactionsListView(dataStore: RecurringTransactionDataStore(apiService, budgetId: budget.id)).navigationBarTitle("recurring_transactions") },
label: { Label("recurring_transactions", systemImage: "arrow.triangle.2.circlepath.circle.fill") })
.keyboardShortcut("4")
label: { Label("recurring_transactions", systemImage: "arrow.triangle.2.circlepath") })
.keyboardShortcut("4")
BudgetListsView()
}
.navigationTitle(budget.name)
}
}.environmentObject(TransactionDataStore(apiService))
.environmentObject(CategoryDataStore(apiService))
.environmentObject(budgetDataStore)
.environmentObject(UserDataStore(apiService))
} else {
ActivityIndicator(isAnimating: .constant(true), style: .large)
}
@ -60,17 +68,17 @@ struct SidebarBudgetView: View {
@ViewBuilder
var body: some View {
mainView
.sheet(isPresented: $authenticationDataStore.showLogin,
onDismiss: {
self.budgetDataStore.getBudgets()
},
content: {
LoginView()
.environmentObject(authenticationDataStore)
.onDisappear {
self.budgetDataStore.getBudgets()
}
})
.sheet(isPresented: $authenticationDataStore.showLogin,
onDismiss: {
self.budgetDataStore.getBudgets()
},
content: {
LoginView()
.environmentObject(authenticationDataStore)
.onDisappear {
self.budgetDataStore.getBudgets()
}
})
.interactiveDismissDisabled(true)
}
}

View file

@ -10,14 +10,18 @@ import SwiftUI
struct TabbedBudgetView: View {
@EnvironmentObject var authenticationDataStore: AuthenticationDataStore
@EnvironmentObject var budgetDataStore: BudgetsDataStore
@EnvironmentObject var categoryDataStore: CategoryDataStore
@StateObject var budgetDataStore: BudgetsDataStore
let apiService: TwigsApiService
@State var isSelectingBudget = true
@State var hasSelectedBudget = false
@State var isAddingTransaction = false
@State var tabSelection: Int = 0
init(_ apiService: TwigsApiService) {
self.apiService = apiService
self._budgetDataStore = StateObject(wrappedValue: BudgetsDataStore(budgetRepository: apiService, categoryRepository: apiService, transactionRepository: apiService))
}
@ViewBuilder
var mainView: some View {
if case let .success(budget) = budgetDataStore.budget {
@ -75,7 +79,10 @@ struct TabbedBudgetView: View {
}
.tag(3)
.keyboardShortcut("4")
}
}.environmentObject(TransactionDataStore(apiService))
.environmentObject(CategoryDataStore(apiService))
.environmentObject(budgetDataStore)
.environmentObject(UserDataStore(apiService))
} else {
ActivityIndicator(isAnimating: .constant(true), style: .large)
}

View file

@ -14,9 +14,7 @@ class TransactionDataStore: ObservableObject {
private var currentRequest: AnyCancellable? = nil
private var sumRequests: [String:AnyCancellable] = [:]
@Published var transactions: [String:Result<OrderedDictionary<String, [Transaction]>, NetworkError>] = ["": .failure(.loading)]
@Published var transaction: Result<Transaction, NetworkError> = .failure(.unknown)
@Published var sums: [String:Result<BalanceResponse, NetworkError>] = ["": .failure(.loading)]
func getTransactions(_ budgetId: String, categoryId: String? = nil, from: Date? = nil, count: Int? = nil, page: Int? = nil) -> String {
@ -31,6 +29,7 @@ class TransactionDataStore: ObservableObject {
budgetIds: [budgetId],
categoryIds: categoryIds,
from: from ?? Date.firstOfMonth,
to: nil,
count: count,
page: page
)

View file

@ -10,7 +10,7 @@ import Foundation
import Combine
protocol TransactionRepository {
func getTransactions(budgetIds: [String], categoryIds: [String]?, from: Date?, count: Int?, page: Int?) -> AnyPublisher<[Transaction], NetworkError>
func getTransactions(budgetIds: [String], categoryIds: [String]?, from: Date?, to: Date?, count: Int?, page: Int?) -> AnyPublisher<[Transaction], NetworkError>
func getTransaction(_ transactionId: String) -> AnyPublisher<Transaction, NetworkError>
func createTransaction(_ transaction: Transaction) -> AnyPublisher<Transaction, NetworkError>
func updateTransaction(_ transaction: Transaction) -> AnyPublisher<Transaction, NetworkError>
@ -18,38 +18,6 @@ protocol TransactionRepository {
func sumTransactions(budgetId: String?, categoryId: String?, from: Date?, to: Date?) -> AnyPublisher<BalanceResponse, NetworkError>
}
class NetworkTransactionRepository: TransactionRepository {
let apiService: TwigsApiService
init(_ apiService: TwigsApiService) {
self.apiService = apiService
}
func getTransactions(budgetIds: [String], categoryIds: [String]?, from: Date?, count: Int?, page: Int?) -> AnyPublisher<[Transaction], NetworkError> {
return apiService.getTransactions(budgetIds: budgetIds, categoryIds: categoryIds, from: from, count: count, page: page)
}
func getTransaction(_ transactionId: String) -> AnyPublisher<Transaction, NetworkError> {
return apiService.getTransaction(transactionId)
}
func createTransaction(_ transaction: Transaction) -> AnyPublisher<Transaction, NetworkError> {
return apiService.newTransaction(transaction)
}
func updateTransaction(_ transaction: Transaction) -> AnyPublisher<Transaction, NetworkError> {
return apiService.updateTransaction(transaction)
}
func deleteTransaction(_ transactionId: String) -> AnyPublisher<Empty, NetworkError> {
return apiService.deleteTransaction(transactionId)
}
func sumTransactions(budgetId: String?, categoryId: String?, from: Date?, to: Date?) -> AnyPublisher<BalanceResponse, NetworkError> {
return apiService.sumTransactions(budgetId: budgetId, categoryId: categoryId, from: from, to: to)
}
}
#if DEBUG
class MockTransactionRepository: TransactionRepository {
static let transaction: Transaction = Transaction(
@ -64,7 +32,7 @@ class MockTransactionRepository: TransactionRepository {
budgetId: MockBudgetRepository.budget.id
)
func getTransactions(budgetIds: [String], categoryIds: [String]?, from: Date?, count: Int?, page: Int?) -> AnyPublisher<[Transaction], NetworkError> {
func getTransactions(budgetIds: [String], categoryIds: [String]?, from: Date?, to: Date?, count: Int?, page: Int?) -> AnyPublisher<[Transaction], NetworkError> {
return Result.Publisher([MockTransactionRepository.transaction]).eraseToAnyPublisher()
}

View file

@ -10,46 +10,28 @@ import SwiftUI
@main
struct TwigsApp: App {
let requestHelper = RequestHelper()
let cacheService = TwigsInMemoryCacheService()
let apiService: TwigsApiService
let budgetRepository: BudgetRepository
let categoryRepository: CategoryRepository
let transactionRepository:TransactionRepository
let userRepository: UserRepository
let dataStoreProvider: DataStoreProvider
@StateObject var authDataStore: AuthenticationDataStore
let apiService: TwigsApiService = TwigsInMemoryCacheService()
init() {
self.apiService = TwigsApiService(requestHelper)
self.budgetRepository = NetworkBudgetRepository(apiService, cacheService: cacheService)
self.categoryRepository = NetworkCategoryRepository(apiService, cacheService: cacheService)
self.transactionRepository = NetworkTransactionRepository(apiService)
self.userRepository = NetworkUserRepository(apiService)
self.dataStoreProvider = DataStoreProvider(
budgetRepository: budgetRepository,
categoryRepository: categoryRepository,
transactionRepository: transactionRepository,
userRepository: userRepository
)
let authDataStore = AuthenticationDataStore(self.apiService)
self._authDataStore = StateObject(wrappedValue: authDataStore)
}
@ViewBuilder
var mainView: some View {
if UIDevice.current.userInterfaceIdiom == .mac || UIDevice.current.userInterfaceIdiom == .pad {
SidebarBudgetView(apiService: apiService)
SidebarBudgetView(apiService)
.environmentObject(authDataStore)
} else {
TabbedBudgetView(apiService: apiService)
TabbedBudgetView(apiService)
.environmentObject(authDataStore)
}
}
var body: some Scene {
WindowGroup {
mainView
.environmentObject(dataStoreProvider.authenticationDataStore())
.environmentObject(dataStoreProvider.budgetsDataStore())
.environmentObject(dataStoreProvider.categoryDataStore())
.environmentObject(dataStoreProvider.transactionDataStore())
.environmentObject(dataStoreProvider.userDataStore())
}
}
}

View file

@ -18,42 +18,6 @@ protocol UserRepository {
func register(username: String, email: String, password: String) -> AnyPublisher<User, NetworkError>
}
class NetworkUserRepository: UserRepository {
let apiService: TwigsApiService
init(_ apiService: TwigsApiService) {
self.apiService = apiService
}
func setToken(_ token: String) {
self.apiService.requestHelper.token = token
}
func getUser(_ id: String) -> AnyPublisher<User, NetworkError> {
return apiService.getUser(id: id)
}
func searchUsers(_ withUsername: String) -> AnyPublisher<[User], NetworkError> {
return apiService.searchUsers(query: withUsername)
}
func setServer(_ server: String) {
var correctServer = server
if !server.starts(with: "http://") && !server.starts(with: "https://") {
correctServer = "http://\(correctServer)"
}
apiService.requestHelper.baseUrl = correctServer
}
func login(username: String, password: String) -> AnyPublisher<LoginResponse, 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 {