Implement creation/editing of recurring transactions
This commit is contained in:
parent
94272aa7f0
commit
e621f659c8
14 changed files with 905 additions and 22 deletions
|
@ -47,14 +47,20 @@
|
|||
806C7850272B700B00FA1375 /* TwigsApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 806C784F272B700B00FA1375 /* TwigsApp.swift */; };
|
||||
8076A84F2809FE8E006B9DC9 /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = 8076A84E2809FE8E006B9DC9 /* ArgumentParser */; };
|
||||
8076A8522809FE99006B9DC9 /* Collections in Frameworks */ = {isa = PBXBuildFile; productRef = 8076A8512809FE99006B9DC9 /* Collections */; };
|
||||
807FEAB52837F71200D05338 /* RecurringTransactionForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 807FEAB42837F71200D05338 /* RecurringTransactionForm.swift */; };
|
||||
807FEAB72838042500D05338 /* MultiPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 807FEAB62838042500D05338 /* MultiPicker.swift */; };
|
||||
80820145275FFD380040996E /* SidebarBudgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80820144275FFD380040996E /* SidebarBudgetView.swift */; };
|
||||
808CA1A728354005002EDD59 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 808CA1A628354005002EDD59 /* XCTest.framework */; };
|
||||
808CA1A928355B30002EDD59 /* BudgetFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 808CA1A828355B30002EDD59 /* BudgetFormView.swift */; };
|
||||
809B942327221EC800B1DAE2 /* CategoryFormSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 809B942227221EC800B1DAE2 /* CategoryFormSheet.swift */; };
|
||||
80A419ED2787C0A00090C515 /* TwigsCli.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80A419EC2787C0A00090C515 /* TwigsCli.swift */; };
|
||||
80A419F52787C1520090C515 /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = 80A419F42787C1520090C515 /* ArgumentParser */; };
|
||||
80AF7A982835ED3B009565C6 /* RecurringTransactionFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80AF7A972835ED3B009565C6 /* RecurringTransactionFormView.swift */; };
|
||||
80D1FC14277C1EF9007F17FB /* InlineLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80D1FC13277C1EF9007F17FB /* InlineLoadingView.swift */; };
|
||||
80D2CE1A2833448500EDD6C2 /* DataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80D2CE192833448500EDD6C2 /* DataStore.swift */; };
|
||||
80FC1BBE28411DD800682F21 /* YearlyFrequencyPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80FC1BBD28411DD800682F21 /* YearlyFrequencyPicker.swift */; };
|
||||
80FC1BC0284146A000682F21 /* WeeklyFrequencyPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80FC1BBF284146A000682F21 /* WeeklyFrequencyPicker.swift */; };
|
||||
80FC1BC2284146CD00682F21 /* MonthlyFrequencyPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80FC1BC1284146CD00682F21 /* MonthlyFrequencyPicker.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
@ -132,6 +138,8 @@
|
|||
8044BA3C2784CC0D009A78D4 /* TransactionForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionForm.swift; sourceTree = "<group>"; };
|
||||
8044BA3E27853054009A78D4 /* CategoryForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryForm.swift; sourceTree = "<group>"; };
|
||||
806C784F272B700B00FA1375 /* TwigsApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwigsApp.swift; sourceTree = "<group>"; };
|
||||
807FEAB42837F71200D05338 /* RecurringTransactionForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecurringTransactionForm.swift; sourceTree = "<group>"; };
|
||||
807FEAB62838042500D05338 /* MultiPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiPicker.swift; sourceTree = "<group>"; };
|
||||
80820144275FFD380040996E /* SidebarBudgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarBudgetView.swift; sourceTree = "<group>"; };
|
||||
808CA1A628354005002EDD59 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
|
||||
808CA1A828355B30002EDD59 /* BudgetFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BudgetFormView.swift; sourceTree = "<group>"; };
|
||||
|
@ -139,8 +147,12 @@
|
|||
809B94242722597800B1DAE2 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
80A419EA2787C0A00090C515 /* twigs-cli */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "twigs-cli"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
80A419EC2787C0A00090C515 /* TwigsCli.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwigsCli.swift; sourceTree = "<group>"; };
|
||||
80AF7A972835ED3B009565C6 /* RecurringTransactionFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecurringTransactionFormView.swift; sourceTree = "<group>"; };
|
||||
80D1FC13277C1EF9007F17FB /* InlineLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineLoadingView.swift; sourceTree = "<group>"; };
|
||||
80D2CE192833448500EDD6C2 /* DataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStore.swift; sourceTree = "<group>"; };
|
||||
80FC1BBD28411DD800682F21 /* YearlyFrequencyPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YearlyFrequencyPicker.swift; sourceTree = "<group>"; };
|
||||
80FC1BBF284146A000682F21 /* WeeklyFrequencyPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeeklyFrequencyPicker.swift; sourceTree = "<group>"; };
|
||||
80FC1BC1284146CD00682F21 /* MonthlyFrequencyPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonthlyFrequencyPicker.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -219,6 +231,10 @@
|
|||
282126BC235CDE1400072D52 /* ProgressView.swift */,
|
||||
80D1FC13277C1EF9007F17FB /* InlineLoadingView.swift */,
|
||||
8005FD5C277EAB0200E48B23 /* MainView.swift */,
|
||||
807FEAB62838042500D05338 /* MultiPicker.swift */,
|
||||
80FC1BBD28411DD800682F21 /* YearlyFrequencyPicker.swift */,
|
||||
80FC1BBF284146A000682F21 /* WeeklyFrequencyPicker.swift */,
|
||||
80FC1BC1284146CD00682F21 /* MonthlyFrequencyPicker.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
|
@ -355,6 +371,8 @@
|
|||
801D08CB275ECEFA00931465 /* RecurringTransactionsListView.swift */,
|
||||
801D08CD275F189E00931465 /* RecurringTransactionsRepository.swift */,
|
||||
801D08D1275FB7DE00931465 /* RecurringTransactionDetailsView.swift */,
|
||||
80AF7A972835ED3B009565C6 /* RecurringTransactionFormView.swift */,
|
||||
807FEAB42837F71200D05338 /* RecurringTransactionForm.swift */,
|
||||
);
|
||||
path = "Recurring Transactions";
|
||||
sourceTree = "<group>";
|
||||
|
@ -538,6 +556,7 @@
|
|||
files = (
|
||||
2821266023555FD300072D52 /* TransactionFormSheet.swift in Sources */,
|
||||
8044BA3D2784CC0D009A78D4 /* TransactionForm.swift in Sources */,
|
||||
80FC1BC0284146A000682F21 /* WeeklyFrequencyPicker.swift in Sources */,
|
||||
28FE6AFC23441E4500D5543E /* CategoryRepository.swift in Sources */,
|
||||
801D08CE275F189E00931465 /* RecurringTransactionsRepository.swift in Sources */,
|
||||
2841022723419A2B00EAFA29 /* TabbedBudgetView.swift in Sources */,
|
||||
|
@ -547,6 +566,7 @@
|
|||
28AC952C233C434800BFB70A /* UserRepository.swift in Sources */,
|
||||
28FE6B04234449DC00D5543E /* TransactionListView.swift in Sources */,
|
||||
28AC94F2233C373900BFB70A /* LoginView.swift in Sources */,
|
||||
80FC1BC2284146CD00682F21 /* MonthlyFrequencyPicker.swift in Sources */,
|
||||
802161D0277647920075761A /* AsyncObservableObject.swift in Sources */,
|
||||
282126BB235CDD3C00072D52 /* BudgetDetailsView.swift in Sources */,
|
||||
80D2CE1A2833448500EDD6C2 /* DataStore.swift in Sources */,
|
||||
|
@ -562,14 +582,18 @@
|
|||
282126A1235929B800072D52 /* ProfileView.swift in Sources */,
|
||||
28AC9529233C433400BFB70A /* TransactionRepository.swift in Sources */,
|
||||
809B942327221EC800B1DAE2 /* CategoryFormSheet.swift in Sources */,
|
||||
807FEAB72838042500D05338 /* MultiPicker.swift in Sources */,
|
||||
28FE6AF62342E4CC00D5543E /* BudgetRepository.swift in Sources */,
|
||||
80FC1BBE28411DD800682F21 /* YearlyFrequencyPicker.swift in Sources */,
|
||||
8044BA3F27853054009A78D4 /* CategoryForm.swift in Sources */,
|
||||
807FEAB52837F71200D05338 /* RecurringTransactionForm.swift in Sources */,
|
||||
80820145275FFD380040996E /* SidebarBudgetView.swift in Sources */,
|
||||
8044BA3927828E9D009A78D4 /* CategoryDataStore.swift in Sources */,
|
||||
284102302342D97300EAFA29 /* BudgetListsView.swift in Sources */,
|
||||
282126BD235CDE1400072D52 /* ProgressView.swift in Sources */,
|
||||
806C7850272B700B00FA1375 /* TwigsApp.swift in Sources */,
|
||||
808CA1A928355B30002EDD59 /* BudgetFormView.swift in Sources */,
|
||||
80AF7A982835ED3B009565C6 /* RecurringTransactionFormView.swift in Sources */,
|
||||
28CE8B9523525F990072BC4C /* Extensions.swift in Sources */,
|
||||
801D08CC275ECEFA00931465 /* RecurringTransactionsListView.swift in Sources */,
|
||||
);
|
||||
|
|
|
@ -33,6 +33,37 @@ class DataStore : ObservableObject {
|
|||
@Published var showBudgetSelection: Bool = true
|
||||
@Published var editingBudget: Bool = false
|
||||
@Published var editingCategory: Bool = false
|
||||
@Published var editingRecurringTransaction: Bool = false
|
||||
|
||||
var currentUserId: String? {
|
||||
get {
|
||||
if case let .success(currentUser) = self.currentUser {
|
||||
return currentUser.id
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var budgetId: String? {
|
||||
get {
|
||||
if case let .success(budget) = self.budget {
|
||||
return budget.id
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var categoryId: String? {
|
||||
get {
|
||||
if case let .success(category) = self.category {
|
||||
return category.id
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(
|
||||
_ apiService: TwigsApiService
|
||||
|
@ -261,8 +292,12 @@ class DataStore : ObservableObject {
|
|||
didSet {
|
||||
if case let .success(transaction) = self.recurringTransaction {
|
||||
self.selectedRecurringTransaction = transaction
|
||||
self.editingRecurringTransaction = false
|
||||
} else if case .empty = recurringTransaction {
|
||||
self.selectedRecurringTransaction = nil
|
||||
self.editingRecurringTransaction = false
|
||||
} else if case .editing(_) = self.recurringTransaction {
|
||||
self.editingRecurringTransaction = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -281,6 +316,31 @@ class DataStore : ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func newRecurringTransaction() {
|
||||
guard case let .success(user) = self.currentUser else {
|
||||
return
|
||||
}
|
||||
guard case let .success(budget) = self.budget else {
|
||||
return
|
||||
}
|
||||
self.recurringTransaction = .editing(RecurringTransaction(createdBy: user.id, budgetId: budget.id))
|
||||
}
|
||||
|
||||
func edit(_ transaction: RecurringTransaction) async {
|
||||
self.recurringTransaction = .editing(transaction)
|
||||
}
|
||||
|
||||
func cancelEditRecurringTransaction() {
|
||||
guard case let .editing(rt) = self.recurringTransaction else {
|
||||
return
|
||||
}
|
||||
if !rt.id.isEmpty {
|
||||
self.recurringTransaction = .success(rt)
|
||||
} else {
|
||||
self.recurringTransaction = .empty
|
||||
}
|
||||
}
|
||||
|
||||
func saveRecurringTransaction(_ transaction: RecurringTransaction) async {
|
||||
self.recurringTransaction = .loading
|
||||
do {
|
||||
|
@ -329,20 +389,12 @@ class DataStore : ObservableObject {
|
|||
}
|
||||
}
|
||||
@Published var selectedTransaction: Transaction? = nil
|
||||
private var budgetId: String = ""
|
||||
private var categoryId: String? = nil
|
||||
|
||||
func getTransactions() async {
|
||||
guard case let .success(budget) = self.budget else {
|
||||
guard let budgetId = self.budgetId else {
|
||||
self.transactions = .error(NetworkError.unknown)
|
||||
return
|
||||
}
|
||||
self.budgetId = budget.id
|
||||
if case let .success(category) = self.category {
|
||||
self.categoryId = category.id
|
||||
} else {
|
||||
self.categoryId = nil
|
||||
}
|
||||
self.transactions = .loading
|
||||
do {
|
||||
var categoryIds: [String] = []
|
||||
|
|
|
@ -28,12 +28,23 @@ struct RecurringTransactionDetailsView: View {
|
|||
Text(transaction.frequency.naturalDescription)
|
||||
Spacer().frame(height: 10)
|
||||
LabeledField(label: "start", value: transaction.start.toLocaleString(), loading: .constant(false), showDivider: true)
|
||||
LabeledField(label: "end", value: transaction.end?.toLocaleString(), loading: .constant(false), showDivider: true)
|
||||
LabeledField(label: "end", value: transaction.finish?.toLocaleString(), loading: .constant(false), showDivider: true)
|
||||
// CategoryLineItem()
|
||||
// BudgetLineItem()
|
||||
// UserLineItem()
|
||||
}.padding()
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button(action: {
|
||||
Task {
|
||||
await dataStore.edit(transaction)
|
||||
}
|
||||
}) {
|
||||
Text("edit")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
156
Twigs/Recurring Transactions/RecurringTransactionForm.swift
Normal file
156
Twigs/Recurring Transactions/RecurringTransactionForm.swift
Normal file
|
@ -0,0 +1,156 @@
|
|||
//
|
||||
// RecurringTransactionForm.swift
|
||||
// Twigs
|
||||
//
|
||||
// Created by William Brawner on 5/20/22.
|
||||
// Copyright © 2022 William Brawner. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import TwigsCore
|
||||
import SwiftUI
|
||||
|
||||
class RecurringTransactionForm: ObservableObject {
|
||||
let apiService: TwigsApiService
|
||||
let dataStore: DataStore
|
||||
let transaction: TwigsCore.RecurringTransaction?
|
||||
let createdBy: String
|
||||
let transactionId: String
|
||||
@Published var title: String
|
||||
@Published var description: String
|
||||
@Published var baseFrequencyUnit: String
|
||||
@Published var frequencyUnit: FrequencyUnit
|
||||
@Published var frequencyCount: String
|
||||
@Published var daysOfWeek: Set<DayOfWeek>
|
||||
@Published var dayOfMonth: DayOfMonth
|
||||
@Published var dayOfYear: DayOfYear
|
||||
@Published var amount: String
|
||||
@Published var start: Date
|
||||
@Published var endCriteria: EndCriteria
|
||||
@Published var end: Date?
|
||||
@Published var type: TransactionType
|
||||
@Published var budgetId: String {
|
||||
didSet {
|
||||
updateCategories()
|
||||
}
|
||||
}
|
||||
@Published var categoryId: String
|
||||
|
||||
@Published var categories: AsyncData<[TwigsCore.Category]> = .empty
|
||||
private var cachedCategories: [TwigsCore.Category] = []
|
||||
let showDelete: Bool
|
||||
|
||||
init(
|
||||
dataStore: DataStore,
|
||||
createdBy: String,
|
||||
budgetId: String,
|
||||
categoryId: String? = nil,
|
||||
transaction: TwigsCore.RecurringTransaction? = nil
|
||||
) {
|
||||
self.apiService = dataStore.apiService
|
||||
self.budgetId = budgetId
|
||||
self.categoryId = categoryId ?? ""
|
||||
self.createdBy = createdBy
|
||||
self.dataStore = dataStore
|
||||
let baseTransaction = transaction ?? TwigsCore.RecurringTransaction(categoryId: categoryId, createdBy: createdBy, budgetId: budgetId)
|
||||
self.transaction = transaction
|
||||
self.transactionId = baseTransaction.id
|
||||
self.title = baseTransaction.title
|
||||
self.description = baseTransaction.description ?? ""
|
||||
self.baseFrequencyUnit = baseTransaction.frequency.unit.baseName
|
||||
self.frequencyUnit = baseTransaction.frequency.unit
|
||||
if case let .weekly(daysOfWeek) = baseTransaction.frequency.unit {
|
||||
self.daysOfWeek = daysOfWeek
|
||||
} else {
|
||||
self.daysOfWeek = Set()
|
||||
}
|
||||
if case let .monthly(dayOfMonth) = baseTransaction.frequency.unit {
|
||||
self.dayOfMonth = dayOfMonth
|
||||
} else {
|
||||
self.dayOfMonth = DayOfMonth(day: 1)!
|
||||
}
|
||||
if case let .yearly(dayOfYear) = baseTransaction.frequency.unit {
|
||||
self.dayOfYear = dayOfYear
|
||||
} else {
|
||||
self.dayOfYear = DayOfYear(month: 1, day: 1)!
|
||||
}
|
||||
self.frequencyCount = String(baseTransaction.frequency.count)
|
||||
self.amount = baseTransaction.amountString
|
||||
self.start = baseTransaction.start
|
||||
self.end = baseTransaction.finish
|
||||
if baseTransaction.finish != nil {
|
||||
self.endCriteria = .onDate
|
||||
} else {
|
||||
self.endCriteria = .never
|
||||
}
|
||||
self.type = baseTransaction.type
|
||||
self.showDelete = !baseTransaction.id.isEmpty
|
||||
}
|
||||
|
||||
func load() async {
|
||||
self.categories = .loading
|
||||
do {
|
||||
let categories = try await apiService.getCategories(budgetId: self.budgetId, expense: nil, archived: false, count: nil, page: nil)
|
||||
self.cachedCategories = categories
|
||||
updateCategories()
|
||||
} catch {
|
||||
self.categories = .error(error)
|
||||
}
|
||||
}
|
||||
|
||||
func save() async {
|
||||
let amount = Double(self.amount) ?? 0.0
|
||||
var frequencyUnit: FrequencyUnit
|
||||
switch self.frequencyUnit {
|
||||
case .daily:
|
||||
frequencyUnit = .daily
|
||||
case .weekly(_):
|
||||
frequencyUnit = .weekly(self.daysOfWeek)
|
||||
case .monthly(_):
|
||||
frequencyUnit = .monthly(self.dayOfMonth)
|
||||
case .yearly(_):
|
||||
frequencyUnit = .yearly(self.dayOfYear)
|
||||
}
|
||||
let components = Calendar.current.dateComponents([.hour, .minute, .second], from: self.start)
|
||||
let time = Time(hours: components.hour!, minutes: components.minute!, seconds: components.second!)!
|
||||
var end: Date? = nil
|
||||
if case self.endCriteria = EndCriteria.onDate, let editedEnd = self.end, editedEnd > self.start {
|
||||
end = editedEnd
|
||||
}
|
||||
await dataStore.saveRecurringTransaction(RecurringTransaction(
|
||||
id: transactionId,
|
||||
title: title,
|
||||
description: description,
|
||||
frequency: Frequency(unit: frequencyUnit, count: Int(frequencyCount) ?? 1, time: time)!,
|
||||
start: start,
|
||||
finish: end,
|
||||
amount: Int(amount * 100.0),
|
||||
categoryId: categoryId,
|
||||
expense: type.toBool(),
|
||||
createdBy: createdBy,
|
||||
budgetId: budgetId
|
||||
))
|
||||
}
|
||||
|
||||
func delete() async {
|
||||
guard let transaction = self.transaction else {
|
||||
return
|
||||
}
|
||||
await dataStore.deleteRecurringTransaction(transaction)
|
||||
}
|
||||
|
||||
private func updateCategories() {
|
||||
self.categories = .success(cachedCategories.filter {
|
||||
$0.expense == self.type.toBool()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
enum EndCriteria: String, Identifiable, CaseIterable {
|
||||
var id: String {
|
||||
return self.rawValue
|
||||
}
|
||||
|
||||
case never = "never"
|
||||
case onDate = "onDate"
|
||||
}
|
144
Twigs/Recurring Transactions/RecurringTransactionFormView.swift
Normal file
144
Twigs/Recurring Transactions/RecurringTransactionFormView.swift
Normal file
|
@ -0,0 +1,144 @@
|
|||
//
|
||||
// RecurringTransactionFormView.swift
|
||||
// Twigs
|
||||
//
|
||||
// Created by William Brawner on 5/18/22.
|
||||
// Copyright © 2022 William Brawner. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import TwigsCore
|
||||
|
||||
struct RecurringTransactionFormView: View {
|
||||
@EnvironmentObject var dataStore: DataStore
|
||||
@ObservedObject var transactionForm: RecurringTransactionForm
|
||||
@State private var showingAlert = false
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
switch self.dataStore.recurringTransaction {
|
||||
case .loading:
|
||||
EmbeddedLoadingView()
|
||||
default:
|
||||
Form {
|
||||
Section {
|
||||
TextField(LocalizedStringKey("prompt_name"), text: $transactionForm.title)
|
||||
.textInputAutocapitalization(.words)
|
||||
TextField(LocalizedStringKey("prompt_description"), text: $transactionForm.description)
|
||||
.textInputAutocapitalization(.sentences)
|
||||
TextField(LocalizedStringKey("prompt_amount"), text: $transactionForm.amount)
|
||||
.keyboardType(.decimalPad)
|
||||
Picker(LocalizedStringKey("prompt_type"), selection: $transactionForm.type) {
|
||||
ForEach(TransactionType.allCases) { type in
|
||||
Text(type.localizedKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
Section {
|
||||
HStack {
|
||||
Text("Repeat every")
|
||||
TextField("count", text: $transactionForm.frequencyCount)
|
||||
.keyboardType(.decimalPad)
|
||||
}
|
||||
Picker(selection: self.$transactionForm.baseFrequencyUnit.animation(), content: {
|
||||
ForEach(FrequencyUnit.allCases) {
|
||||
Text(LocalizedStringKey($0.baseName)).tag($0.baseName)
|
||||
}
|
||||
}, label: {
|
||||
Text("frequency")
|
||||
})
|
||||
.pickerStyle(.segmented)
|
||||
FrequencyPickerView(
|
||||
frequencyUnit: $transactionForm.baseFrequencyUnit,
|
||||
daysOfWeek: $transactionForm.daysOfWeek,
|
||||
dayOfMonth: $transactionForm.dayOfMonth,
|
||||
dayOfYear: $transactionForm.dayOfYear
|
||||
)
|
||||
}
|
||||
Section(footer: Text("note_end_optional")) {
|
||||
DatePicker(selection: $transactionForm.start, label: { Text(LocalizedStringKey("prompt_start")) })
|
||||
Picker(LocalizedStringKey("prompt_end"), selection: $transactionForm.endCriteria.animation()) {
|
||||
ForEach(EndCriteria.allCases) { criteria in
|
||||
Text(LocalizedStringKey(criteria.rawValue)).tag(criteria)
|
||||
}
|
||||
}
|
||||
if case .onDate = transactionForm.endCriteria {
|
||||
DatePicker(
|
||||
"",
|
||||
selection: Binding<Date>(get: {transactionForm.end ?? Date()}, set: {transactionForm.end = $0})
|
||||
)
|
||||
}
|
||||
}
|
||||
Section {
|
||||
CategoryPicker(categories: $transactionForm.categories, categoryId: $transactionForm.categoryId)
|
||||
}
|
||||
if transactionForm.showDelete {
|
||||
Button(action: {
|
||||
self.showingAlert = true
|
||||
}) {
|
||||
Text(LocalizedStringKey("delete"))
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
.alert(isPresented:$showingAlert) {
|
||||
Alert(
|
||||
title: Text(LocalizedStringKey("confirm_delete")),
|
||||
message: Text(LocalizedStringKey("cannot_undo")),
|
||||
primaryButton: .destructive(
|
||||
Text(LocalizedStringKey("delete")),
|
||||
action: { Task { await transactionForm.delete() }}
|
||||
),
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
}.environmentObject(transactionForm)
|
||||
.task {
|
||||
await transactionForm.load()
|
||||
}
|
||||
.navigationTitle(transactionForm.transactionId.isEmpty ? "add_recurring_transaction" : "edit_recurring_transaction")
|
||||
.navigationBarItems(
|
||||
leading: Button("cancel", action: { dataStore.cancelEditRecurringTransaction() }),
|
||||
trailing: Button("save", action: {
|
||||
Task {
|
||||
await transactionForm.save()
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FrequencyPickerView: View {
|
||||
@Binding var frequencyUnit: String
|
||||
@Binding var daysOfWeek: Set<DayOfWeek>
|
||||
@Binding var dayOfMonth: DayOfMonth
|
||||
@Binding var dayOfYear: DayOfYear
|
||||
|
||||
@ViewBuilder
|
||||
var body: some View {
|
||||
switch frequencyUnit {
|
||||
case "week":
|
||||
WeeklyFrequencyPicker(selection: $daysOfWeek)
|
||||
case "month":
|
||||
MonthlyFrequencyPicker(dayOfMonth: $dayOfMonth)
|
||||
case "year":
|
||||
YearlyFrequencyPicker(dayOfYear: $dayOfYear)
|
||||
default:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RecurringTransactionFormView_Previews: PreviewProvider {
|
||||
static var dataStore = DataStore(TwigsInMemoryCacheService())
|
||||
static var previews: some View {
|
||||
RecurringTransactionFormView(transactionForm: RecurringTransactionForm(
|
||||
dataStore: dataStore,
|
||||
createdBy: MockUserRepository.currentUser.id,
|
||||
budgetId: MockBudgetRepository.budget.id
|
||||
)).environmentObject(dataStore)
|
||||
}
|
||||
}
|
|
@ -24,6 +24,29 @@ struct RecurringTransactionsListView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button(action: {
|
||||
dataStore.newRecurringTransaction()
|
||||
}, label: {
|
||||
Image(systemName: "plus").padding()
|
||||
})
|
||||
}
|
||||
}
|
||||
.sheet(
|
||||
isPresented: $dataStore.editingRecurringTransaction,
|
||||
onDismiss: {
|
||||
dataStore.cancelEditRecurringTransaction()
|
||||
},
|
||||
content: {
|
||||
RecurringTransactionFormView(transactionForm: RecurringTransactionForm(
|
||||
dataStore: dataStore,
|
||||
createdBy: dataStore.currentUserId ?? "",
|
||||
budgetId: dataStore.budgetId ?? "",
|
||||
categoryId: dataStore.selectedRecurringTransaction?.categoryId ?? dataStore.categoryId,
|
||||
transaction: dataStore.selectedRecurringTransaction
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ class MockRecurringTransactionRepository: RecurringTransactionsRepository {
|
|||
description: "A mock transaction used for testing",
|
||||
frequency: Frequency(unit: .daily, count: 1, time: Time(from: "09:00:00")!)!,
|
||||
start: Date(),
|
||||
end: nil,
|
||||
finish: nil,
|
||||
amount: 10000,
|
||||
categoryId: MockCategoryRepository.category.id,
|
||||
expense: true,
|
||||
|
|
|
@ -34,8 +34,8 @@ struct TransactionFormSheet: View {
|
|||
Text(type.localizedKey)
|
||||
}
|
||||
}
|
||||
BudgetPicker()
|
||||
CategoryPicker()
|
||||
BudgetPicker(budgets: $transactionForm.budgets, budgetId: $transactionForm.budgetId)
|
||||
CategoryPicker(categories: $transactionForm.categories, categoryId: $transactionForm.categoryId)
|
||||
if transactionForm.showDelete {
|
||||
Button(action: {
|
||||
self.showingAlert = true
|
||||
|
@ -76,18 +76,19 @@ struct TransactionFormSheet: View {
|
|||
}
|
||||
|
||||
struct BudgetPicker: View {
|
||||
@EnvironmentObject var transactionForm: TransactionForm
|
||||
@Binding var budgets: AsyncData<[Budget]>
|
||||
@Binding var budgetId: String
|
||||
|
||||
@ViewBuilder
|
||||
var body: some View {
|
||||
if case let .success(budgets) = self.transactionForm.budgets {
|
||||
Picker(LocalizedStringKey("prompt_budget"), selection: $transactionForm.budgetId) {
|
||||
if case let .success(budgets) = budgets {
|
||||
Picker(LocalizedStringKey("prompt_budget"), selection: $budgetId) {
|
||||
ForEach(budgets) { budget in
|
||||
Text(budget.name)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Picker(LocalizedStringKey("prompt_budget"), selection: $transactionForm.budgetId) {
|
||||
Picker(LocalizedStringKey("prompt_budget"), selection: $budgetId) {
|
||||
Text("")
|
||||
}
|
||||
}
|
||||
|
@ -95,14 +96,15 @@ struct BudgetPicker: View {
|
|||
}
|
||||
|
||||
struct CategoryPicker: View {
|
||||
@EnvironmentObject var transactionForm: TransactionForm
|
||||
|
||||
@Binding var categories: AsyncData<[TwigsCore.Category]>
|
||||
@Binding var categoryId: String
|
||||
|
||||
@ViewBuilder
|
||||
var body: some View {
|
||||
if case let .success(categories) = self.transactionForm.categories {
|
||||
Picker(LocalizedStringKey("prompt_category"), selection: $transactionForm.categoryId) {
|
||||
if case let .success(categories) = categories {
|
||||
Picker(LocalizedStringKey("prompt_category"), selection: $categoryId) {
|
||||
ForEach(categories) { category in
|
||||
Text(category.title)
|
||||
Text(category.title).tag(category.id)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
124
Twigs/Views/MonthlyFrequencyPicker.swift
Normal file
124
Twigs/Views/MonthlyFrequencyPicker.swift
Normal file
|
@ -0,0 +1,124 @@
|
|||
//
|
||||
// MonthlyFrequencyPicker.swift
|
||||
// Twigs
|
||||
//
|
||||
// Created by William Brawner on 5/27/22.
|
||||
// Copyright © 2022 William Brawner. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import TwigsCore
|
||||
|
||||
struct MonthlyFrequencyPicker: UIViewRepresentable {
|
||||
@Binding var dayOfMonth: DayOfMonth
|
||||
@State var dayOfWeek: Int
|
||||
@State var intDay: Int
|
||||
@State var ordinalDay: Int
|
||||
|
||||
init(dayOfMonth: Binding<DayOfMonth>) {
|
||||
self._dayOfMonth = dayOfMonth
|
||||
if case let .fixed(intDay) = dayOfMonth.wrappedValue {
|
||||
self.intDay = intDay - 1
|
||||
self.ordinalDay = 0
|
||||
self.dayOfWeek = 0
|
||||
} else if case let .ordinal(ordinalDay, dayOfWeek) = dayOfMonth.wrappedValue {
|
||||
self.intDay = 0
|
||||
self.ordinalDay = Ordinal.allCases.firstIndex(of: ordinalDay)!
|
||||
self.dayOfWeek = DayOfWeek.allCases.firstIndex(of: dayOfWeek)!
|
||||
} else {
|
||||
self.intDay = 0
|
||||
self.dayOfWeek = 0
|
||||
self.ordinalDay = 0
|
||||
}
|
||||
}
|
||||
|
||||
func makeCoordinator() -> MonthlyFrequencyPicker.Coordinator {
|
||||
Coordinator(self, selectedOrdinal: $ordinalDay, selectedDay: $intDay, selectedDayOfWeek: $dayOfWeek)
|
||||
}
|
||||
|
||||
func makeUIView(context: UIViewRepresentableContext<MonthlyFrequencyPicker>) -> UIPickerView {
|
||||
let picker = UIPickerView(frame: .zero)
|
||||
picker.dataSource = context.coordinator
|
||||
picker.delegate = context.coordinator
|
||||
return picker
|
||||
}
|
||||
|
||||
func updateUIView(_ view: UIPickerView, context: UIViewRepresentableContext<MonthlyFrequencyPicker>) {
|
||||
view.selectRow(ordinalDay, inComponent: 0, animated: false)
|
||||
let component2Selection = ordinalDay == 0 ? intDay : dayOfWeek
|
||||
view.selectRow(component2Selection, inComponent: 1, animated: true)
|
||||
view.reloadComponent(1)
|
||||
}
|
||||
|
||||
class Coordinator: NSObject, UIPickerViewDataSource, UIPickerViewDelegate {
|
||||
let ordinals = Ordinal.allCases.map {
|
||||
$0.rawValue.lowercased()
|
||||
}
|
||||
|
||||
var parent: MonthlyFrequencyPicker
|
||||
@Binding var selectedOrdinal: Int {
|
||||
didSet {
|
||||
// This is a workaround for the pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) function not getting the correct values for the selectedOrdinal
|
||||
ordinal = self.selectedOrdinal
|
||||
}
|
||||
}
|
||||
@Binding var selectedDay: Int
|
||||
@Binding var selectedDayOfWeek: Int
|
||||
|
||||
private var ordinal = 0
|
||||
|
||||
init(_ pickerView: MonthlyFrequencyPicker, selectedOrdinal: Binding<Int>, selectedDay: Binding<Int>, selectedDayOfWeek: Binding<Int>) {
|
||||
self.parent = pickerView
|
||||
self._selectedOrdinal = selectedOrdinal
|
||||
self._selectedDay = selectedDay
|
||||
self._selectedDayOfWeek = selectedDayOfWeek
|
||||
}
|
||||
|
||||
func numberOfComponents(in pickerView: UIPickerView) -> Int {
|
||||
return 2
|
||||
}
|
||||
|
||||
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
|
||||
if component == 0 {
|
||||
return ordinals.count
|
||||
}
|
||||
if ordinal == 0 {
|
||||
return 31
|
||||
} else {
|
||||
return 7
|
||||
}
|
||||
}
|
||||
|
||||
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
|
||||
if component == 0 {
|
||||
return NSLocalizedString(ordinals[row], comment: "")
|
||||
}
|
||||
|
||||
if ordinal == 0 {
|
||||
return String(row + 1)
|
||||
} else {
|
||||
return NSLocalizedString(DayOfWeek.allCases[row].rawValue, comment: "")
|
||||
}
|
||||
}
|
||||
|
||||
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
|
||||
if component == 0 {
|
||||
selectedOrdinal = row
|
||||
return
|
||||
}
|
||||
if ordinal == 0 {
|
||||
selectedDay = row
|
||||
} else {
|
||||
selectedDayOfWeek = row
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MonthlyFrequencyPicker_Previews: PreviewProvider {
|
||||
@State static var dayOfMonth: DayOfMonth = .fixed(1)
|
||||
|
||||
static var previews: some View {
|
||||
MonthlyFrequencyPicker(dayOfMonth: $dayOfMonth)
|
||||
}
|
||||
}
|
65
Twigs/Views/MultiPicker.swift
Normal file
65
Twigs/Views/MultiPicker.swift
Normal file
|
@ -0,0 +1,65 @@
|
|||
//
|
||||
// MultiPicker.swift
|
||||
// Twigs
|
||||
//
|
||||
// Created by William Brawner on 5/20/22.
|
||||
// Copyright © 2022 William Brawner. All rights reserved.
|
||||
// Adapted from https://stackoverflow.com/a/58664469
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct MultiPicker: UIViewRepresentable {
|
||||
var data: [[String]]
|
||||
@Binding var selections: [Int]
|
||||
|
||||
func makeCoordinator() -> MultiPicker.Coordinator {
|
||||
Coordinator(self)
|
||||
}
|
||||
|
||||
func makeUIView(context: UIViewRepresentableContext<MultiPicker>) -> UIPickerView {
|
||||
let picker = UIPickerView(frame: .zero)
|
||||
|
||||
picker.dataSource = context.coordinator
|
||||
picker.delegate = context.coordinator
|
||||
|
||||
return picker
|
||||
}
|
||||
|
||||
func updateUIView(_ view: UIPickerView, context: UIViewRepresentableContext<MultiPicker>) {
|
||||
for i in 0...(self.selections.count - 1) {
|
||||
view.selectRow(self.selections[i], inComponent: i, animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
class Coordinator: NSObject, UIPickerViewDataSource, UIPickerViewDelegate {
|
||||
var parent: MultiPicker
|
||||
|
||||
init(_ pickerView: MultiPicker) {
|
||||
self.parent = pickerView
|
||||
}
|
||||
|
||||
func numberOfComponents(in pickerView: UIPickerView) -> Int {
|
||||
return self.parent.data.count
|
||||
}
|
||||
|
||||
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
|
||||
return self.parent.data[component].count
|
||||
}
|
||||
|
||||
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
|
||||
return self.parent.data[component][row]
|
||||
}
|
||||
|
||||
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
|
||||
self.parent.selections[component] = row
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MultiPicker_Previews: PreviewProvider {
|
||||
@State static var selections: [Int] = [0, 0]
|
||||
|
||||
static var previews: some View {
|
||||
MultiPicker(data: [["a", "b", "c"], ["one", "two", "three"]], selections: $selections)
|
||||
}
|
||||
}
|
62
Twigs/Views/WeeklyFrequencyPicker.swift
Normal file
62
Twigs/Views/WeeklyFrequencyPicker.swift
Normal file
|
@ -0,0 +1,62 @@
|
|||
//
|
||||
// WeeklyFrequencyPicker.swift
|
||||
// Twigs
|
||||
//
|
||||
// Created by William Brawner on 5/27/22.
|
||||
// Copyright © 2022 William Brawner. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import TwigsCore
|
||||
|
||||
struct WeeklyFrequencyPicker: View {
|
||||
@Binding var selection: Set<DayOfWeek>
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
HStack {
|
||||
ForEach(DayOfWeek.allCases.slice(count: 4, page: 1)) { dayOfWeek in
|
||||
Toggle(isOn: .constant(selection.contains(dayOfWeek))) {
|
||||
Text(LocalizedStringKey(dayOfWeek.rawValue.lowercased()))
|
||||
.lineLimit(1)
|
||||
.onTapGesture {
|
||||
if selection.contains(dayOfWeek) {
|
||||
selection.remove(dayOfWeek)
|
||||
} else {
|
||||
selection.update(with: dayOfWeek)
|
||||
}
|
||||
}
|
||||
}
|
||||
.toggleStyle(.button)
|
||||
.onSubmit {
|
||||
print("Toggle selected for \(dayOfWeek)")
|
||||
}
|
||||
}
|
||||
}
|
||||
HStack {
|
||||
ForEach(DayOfWeek.allCases.slice(count: 4, page: 2)) { dayOfWeek in
|
||||
Toggle(isOn: .constant(selection.contains(dayOfWeek))) {
|
||||
Text(LocalizedStringKey(dayOfWeek.rawValue.lowercased()))
|
||||
.lineLimit(1)
|
||||
.onTapGesture {
|
||||
if selection.contains(dayOfWeek) {
|
||||
selection.remove(dayOfWeek)
|
||||
} else {
|
||||
selection.update(with: dayOfWeek)
|
||||
}
|
||||
}
|
||||
}
|
||||
.toggleStyle(.button)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct WeeklyFrequencyPicker_Previews: PreviewProvider {
|
||||
@State static var selection: Set<DayOfWeek> = Set()
|
||||
|
||||
static var previews: some View {
|
||||
WeeklyFrequencyPicker(selection: $selection)
|
||||
}
|
||||
}
|
118
Twigs/Views/YearlyFrequencyPicker.swift
Normal file
118
Twigs/Views/YearlyFrequencyPicker.swift
Normal file
|
@ -0,0 +1,118 @@
|
|||
//
|
||||
// YearlyFrequencyPicker.swift
|
||||
// Twigs
|
||||
//
|
||||
// Created by William Brawner on 5/27/22.
|
||||
// Copyright © 2022 William Brawner. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import TwigsCore
|
||||
|
||||
struct YearlyFrequencyPicker: UIViewRepresentable {
|
||||
@Binding var dayOfYear: DayOfYear
|
||||
@State var selectedMonth: Int {
|
||||
didSet {
|
||||
selectedDay = min(self.selectedDay, DayOfYear.maxDays(inMonth: self.selectedMonth + 1) - 1)
|
||||
}
|
||||
}
|
||||
@State var selectedDay: Int {
|
||||
didSet {
|
||||
if let dayOfYear = DayOfYear(month: selectedMonth + 1, day: selectedDay + 1) {
|
||||
self.dayOfYear = dayOfYear
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(dayOfYear: Binding<DayOfYear>) {
|
||||
self._dayOfYear = dayOfYear
|
||||
self.selectedMonth = dayOfYear.wrappedValue.month - 1
|
||||
self.selectedDay = dayOfYear.wrappedValue.day - 1
|
||||
}
|
||||
|
||||
func makeCoordinator() -> YearlyFrequencyPicker.Coordinator {
|
||||
Coordinator(self, selectedMonth: $selectedMonth, selectedDay: $selectedDay)
|
||||
}
|
||||
|
||||
func makeUIView(context: UIViewRepresentableContext<YearlyFrequencyPicker>) -> UIPickerView {
|
||||
let picker = UIPickerView(frame: .zero)
|
||||
picker.dataSource = context.coordinator
|
||||
picker.delegate = context.coordinator
|
||||
return picker
|
||||
}
|
||||
|
||||
func updateUIView(_ view: UIPickerView, context: UIViewRepresentableContext<YearlyFrequencyPicker>) {
|
||||
view.selectRow(selectedMonth, inComponent: 0, animated: false)
|
||||
view.selectRow(selectedDay, inComponent: 1, animated: true)
|
||||
view.reloadComponent(1)
|
||||
}
|
||||
|
||||
class Coordinator: NSObject, UIPickerViewDataSource, UIPickerViewDelegate {
|
||||
let months = [
|
||||
"january",
|
||||
"february",
|
||||
"march",
|
||||
"april",
|
||||
"may",
|
||||
"june",
|
||||
"july",
|
||||
"august",
|
||||
"september",
|
||||
"october",
|
||||
"november",
|
||||
"december"
|
||||
]
|
||||
|
||||
var parent: YearlyFrequencyPicker
|
||||
@Binding var selectedMonth: Int {
|
||||
didSet {
|
||||
// This is a workaround for the pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) function not getting the correct values for selectedMonth
|
||||
month = self.selectedMonth
|
||||
}
|
||||
}
|
||||
@Binding var selectedDay: Int
|
||||
|
||||
private var month = 0
|
||||
|
||||
init(_ pickerView: YearlyFrequencyPicker, selectedMonth: Binding<Int>, selectedDay: Binding<Int> ) {
|
||||
self.parent = pickerView
|
||||
self._selectedMonth = selectedMonth
|
||||
self._selectedDay = selectedDay
|
||||
}
|
||||
|
||||
func numberOfComponents(in pickerView: UIPickerView) -> Int {
|
||||
return 2
|
||||
}
|
||||
|
||||
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
|
||||
if component == 0 {
|
||||
return months.count
|
||||
}
|
||||
return DayOfYear.maxDays(inMonth: month + 1)
|
||||
}
|
||||
|
||||
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
|
||||
if component == 0 {
|
||||
return NSLocalizedString(months[row], comment: "")
|
||||
}
|
||||
|
||||
return String(row + 1)
|
||||
}
|
||||
|
||||
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
|
||||
if component == 0 {
|
||||
selectedMonth = row
|
||||
} else {
|
||||
selectedDay = row
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct YearlyFrequencyPicker_Previews: PreviewProvider {
|
||||
@State static var dayOfYear: DayOfYear = (DayOfYear(month: 1, day: 1)!)
|
||||
|
||||
static var previews: some View {
|
||||
YearlyFrequencyPicker(dayOfYear: $dayOfYear)
|
||||
}
|
||||
}
|
|
@ -80,3 +80,54 @@
|
|||
// MARK: Recurring Transactions
|
||||
"recurring" = "Recurring";
|
||||
"recurring_transactions" = "Recurring Transactions";
|
||||
"add_recurring_transaction" = "Add Recurring Transaction";
|
||||
"edit_recurring_transaction" = "Edit Recurring Transaction";
|
||||
"prompt_start" = "Start";
|
||||
"prompt_end" = "End";
|
||||
"frequency" = "Frequency";
|
||||
"note_end_optional" = "Note: The end date is optional";
|
||||
|
||||
"day" = "Day";
|
||||
"week" = "Week";
|
||||
"month" = "Month";
|
||||
"year" = "Year";
|
||||
|
||||
"sunday" = "SUN";
|
||||
"monday" = "MON";
|
||||
"tuesday" = "TUES";
|
||||
"wednesday" = "WED";
|
||||
"thursday" = "THURS";
|
||||
"friday" = "FRI";
|
||||
"saturday" = "SAT";
|
||||
|
||||
"first" = "First";
|
||||
"second" = "Second";
|
||||
"third" = "Third";
|
||||
"fourth" = "Fourth";
|
||||
"last" = "Last";
|
||||
|
||||
"SUNDAY" = "Sunday";
|
||||
"MONDAY" = "Monday";
|
||||
"TUESDAY" = "Tuesday";
|
||||
"WEDNESDAY" = "Wednesday";
|
||||
"THURSDAY" = "Thursday";
|
||||
"FRIDAY" = "Friday";
|
||||
"SATURDAY" = "Saturday";
|
||||
|
||||
"january" = "January";
|
||||
"february" = "February";
|
||||
"march" = "March";
|
||||
"april" = "April";
|
||||
"may" = "May";
|
||||
"june" = "June";
|
||||
"july" = "July";
|
||||
"august" = "August";
|
||||
"september" = "September";
|
||||
"october" = "October";
|
||||
"november" = "November";
|
||||
"december" = "December";
|
||||
|
||||
"start" = "Start";
|
||||
"end" = "End";
|
||||
"never" = "Never";
|
||||
"onDate" = "On Date";
|
||||
|
|
|
@ -81,3 +81,54 @@
|
|||
// MARK: Recurring Transactions
|
||||
"recurring" = "Recurrentes";
|
||||
"recurring_transactions" = "Transacciones Recurrentes";
|
||||
"add_recurring_transaction" = "Agregar Transacción Recurrente";
|
||||
"edit_recurring_transaction" = "Editar Transacción Recurrente";
|
||||
"prompt_start" = "Inicio";
|
||||
"prompt_end" = "Final";
|
||||
"note_end_optional" = "Nota: La fecha final es opcional";
|
||||
"frequency" = "Frequencia";
|
||||
|
||||
"day" = "Día";
|
||||
"week" = "Semana";
|
||||
"month" = "Mes";
|
||||
"year" = "Año";
|
||||
|
||||
"sunday" = "DOM";
|
||||
"monday" = "LUN";
|
||||
"tuesday" = "MAR";
|
||||
"wednesday" = "MIE";
|
||||
"thursday" = "JUE";
|
||||
"friday" = "VIE";
|
||||
"saturday" = "SAB";
|
||||
|
||||
"first" = "Primer";
|
||||
"second" = "Segundo";
|
||||
"third" = "Tercer";
|
||||
"fourth" = "Cuarto";
|
||||
"last" = "Último";
|
||||
|
||||
"SUNDAY" = "domingo";
|
||||
"MONDAY" = "lunes";
|
||||
"TUESDAY" = "martes";
|
||||
"WEDNESDAY" = "miércoles";
|
||||
"THURSDAY" = "jueves";
|
||||
"FRIDAY" = "viernes";
|
||||
"SATURDAY" = "sábado";
|
||||
|
||||
"january" = "enero";
|
||||
"february" = "febrero";
|
||||
"march" = "marzo";
|
||||
"april" = "abril";
|
||||
"may" = "mayo";
|
||||
"june" = "junio";
|
||||
"july" = "julio";
|
||||
"august" = "agosto";
|
||||
"september" = "septiembre";
|
||||
"october" = "octubre";
|
||||
"november" = "noviembre";
|
||||
"december" = "diciembre";
|
||||
|
||||
"start" = "Inicio";
|
||||
"end" = "Fin";
|
||||
"never" = "Nunca";
|
||||
"onDate" = "Fecha";
|
||||
|
|
Loading…
Reference in a new issue