Update version
This commit is contained in:
@@ -3,6 +3,20 @@ import UniformTypeIdentifiers
|
||||
import UIKit
|
||||
|
||||
struct ImportDataView: View {
|
||||
enum ImportContext {
|
||||
case settings
|
||||
case onboarding
|
||||
}
|
||||
|
||||
enum AccountSelection: String, CaseIterable, Identifiable {
|
||||
case existing
|
||||
case new
|
||||
|
||||
var id: String { rawValue }
|
||||
}
|
||||
|
||||
let importContext: ImportContext
|
||||
|
||||
@EnvironmentObject private var iapService: IAPService
|
||||
@EnvironmentObject private var accountStore: AccountStore
|
||||
@EnvironmentObject private var tabSelection: TabSelectionStore
|
||||
@@ -15,10 +29,24 @@ struct ImportDataView: View {
|
||||
@State private var isImporting = false
|
||||
@State private var importProgress: Double = 0
|
||||
@State private var importStatus = "Preparing import"
|
||||
@State private var accountSelection: AccountSelection = .existing
|
||||
@State private var selectedAccountId: UUID?
|
||||
@State private var newAccountName = ""
|
||||
@State private var accountErrorMessage: String?
|
||||
|
||||
private let accountRepository = AccountRepository()
|
||||
|
||||
init(importContext: ImportContext = .settings) {
|
||||
self.importContext = importContext
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
List {
|
||||
if shouldShowAccountSelection {
|
||||
accountSection
|
||||
}
|
||||
|
||||
Section {
|
||||
Picker("Format", selection: $selectedFormat) {
|
||||
Text("CSV").tag(ImportService.ImportFormat.csv)
|
||||
@@ -65,7 +93,7 @@ struct ImportDataView: View {
|
||||
} header: {
|
||||
Text("Format Guide")
|
||||
} footer: {
|
||||
Text(iapService.isPremium ? "Accounts are imported as provided." : "Free users import into the Personal account.")
|
||||
Text(importFooterText)
|
||||
}
|
||||
|
||||
Section {
|
||||
@@ -117,6 +145,18 @@ struct ImportDataView: View {
|
||||
} message: {
|
||||
Text(errorMessage ?? "")
|
||||
}
|
||||
.onAppear {
|
||||
if selectedAccountId == nil {
|
||||
selectedAccountId = accountStore.selectedAccount?.id ?? accountStore.accounts.first?.id
|
||||
}
|
||||
}
|
||||
.onChange(of: accountSelection) { _, _ in
|
||||
accountErrorMessage = nil
|
||||
}
|
||||
.onChange(of: newAccountName) { _, _ in
|
||||
guard accountSelection == .new else { return }
|
||||
accountErrorMessage = validateNewAccountName()
|
||||
}
|
||||
}
|
||||
.presentationDetents([.large])
|
||||
}
|
||||
@@ -178,6 +218,78 @@ Personal,Stocks,Index Fund,2024-01-01,15000,12000,Long-term
|
||||
}
|
||||
}
|
||||
|
||||
private var shouldShowAccountSelection: Bool {
|
||||
importContext == .onboarding
|
||||
}
|
||||
|
||||
private var importFooterText: String {
|
||||
if importContext == .onboarding {
|
||||
return iapService.isPremium
|
||||
? "Import will be added to the selected account."
|
||||
: "Free users import into the existing account."
|
||||
}
|
||||
return iapService.isPremium
|
||||
? "Accounts are imported as provided."
|
||||
: "Free users import into the selected account."
|
||||
}
|
||||
|
||||
private var accountSection: some View {
|
||||
Section {
|
||||
if iapService.isPremium {
|
||||
Picker("Account", selection: $accountSelection) {
|
||||
Text("Existing").tag(AccountSelection.existing)
|
||||
Text("New").tag(AccountSelection.new)
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
.disabled(isImporting)
|
||||
|
||||
if accountSelection == .existing {
|
||||
Picker("Import into", selection: $selectedAccountId) {
|
||||
ForEach(accountStore.accounts) { account in
|
||||
Text(account.name).tag(Optional(account.id))
|
||||
}
|
||||
}
|
||||
.disabled(isImporting)
|
||||
} else {
|
||||
TextField("New account name", text: $newAccountName)
|
||||
.disabled(isImporting)
|
||||
}
|
||||
} else {
|
||||
HStack {
|
||||
Text("Account")
|
||||
Spacer()
|
||||
Text(selectedAccountName ?? "Personal")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text("Account")
|
||||
} footer: {
|
||||
if let accountErrorMessage {
|
||||
Text(accountErrorMessage)
|
||||
.foregroundColor(.negativeRed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var selectedAccountName: String? {
|
||||
accountStore.accounts.first { $0.id == selectedAccountId }?.name
|
||||
?? accountStore.selectedAccount?.name
|
||||
?? accountStore.accounts.first?.name
|
||||
}
|
||||
|
||||
private func validateNewAccountName() -> String? {
|
||||
let trimmed = newAccountName.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !trimmed.isEmpty else {
|
||||
return "Enter a name for the new account."
|
||||
}
|
||||
let normalized = trimmed.lowercased()
|
||||
let exists = accountStore.accounts.contains {
|
||||
$0.name.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == normalized
|
||||
}
|
||||
return exists ? "An account with this name already exists." : nil
|
||||
}
|
||||
|
||||
private func handleImport(_ result: Result<[URL], Error>) {
|
||||
do {
|
||||
let urls = try result.get()
|
||||
@@ -243,9 +355,30 @@ Personal,Stocks,Index Fund,2024-01-01,15000,12000,Long-term
|
||||
}
|
||||
|
||||
private func handleImportContent(_ content: String) {
|
||||
let allowMultipleAccounts = iapService.isPremium
|
||||
let defaultAccountName = accountStore.selectedAccount?.name
|
||||
let shouldForceSingleAccount = importContext == .onboarding
|
||||
let allowMultipleAccounts = iapService.isPremium && !shouldForceSingleAccount
|
||||
var defaultAccountName = accountStore.selectedAccount?.name
|
||||
?? accountStore.accounts.first?.name
|
||||
|
||||
if shouldForceSingleAccount && iapService.isPremium {
|
||||
if accountSelection == .new {
|
||||
accountErrorMessage = validateNewAccountName()
|
||||
guard accountErrorMessage == nil else { return }
|
||||
let trimmed = newAccountName.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let currency = AppSettings.getOrCreate(in: CoreDataStack.shared.viewContext).currency
|
||||
let account = accountRepository.createAccount(
|
||||
name: trimmed,
|
||||
currency: currency,
|
||||
inputMode: .simple,
|
||||
notificationFrequency: .monthly,
|
||||
customFrequencyMonths: 1
|
||||
)
|
||||
defaultAccountName = account.name
|
||||
} else {
|
||||
defaultAccountName = selectedAccountName
|
||||
}
|
||||
}
|
||||
|
||||
isImporting = true
|
||||
importProgress = 0
|
||||
importStatus = "Parsing file"
|
||||
|
||||
@@ -2,7 +2,7 @@ import SwiftUI
|
||||
import StoreKit
|
||||
|
||||
struct SettingsView: View {
|
||||
@EnvironmentObject var iapService: IAPService
|
||||
private let iapService: IAPService
|
||||
@StateObject private var viewModel: SettingsViewModel
|
||||
@AppStorage("calmModeEnabled") private var calmModeEnabled = true
|
||||
@AppStorage("cloudSyncEnabled") private var cloudSyncEnabled = false
|
||||
@@ -19,8 +19,9 @@ struct SettingsView: View {
|
||||
@State private var showingRestartAlert = false
|
||||
@State private var didLoadCloudSync = false
|
||||
|
||||
init() {
|
||||
_viewModel = StateObject(wrappedValue: SettingsViewModel(iapService: IAPService()))
|
||||
init(iapService: IAPService) {
|
||||
self.iapService = iapService
|
||||
_viewModel = StateObject(wrappedValue: SettingsViewModel(iapService: iapService))
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@@ -221,7 +222,7 @@ struct SettingsView: View {
|
||||
.font(.headline)
|
||||
.foregroundColor(.primary)
|
||||
|
||||
Text("Unlock all features for €4.69")
|
||||
Text("Unlock all features for \(iapService.formattedPrice)")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
@@ -705,7 +706,6 @@ struct PinSetupView: View {
|
||||
}
|
||||
|
||||
#Preview {
|
||||
SettingsView()
|
||||
.environmentObject(IAPService())
|
||||
SettingsView(iapService: IAPService())
|
||||
.environmentObject(AccountStore(iapService: IAPService()))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user