Primera build enviada
This commit is contained in:
@@ -83,14 +83,28 @@ class SettingsViewModel: ObservableObject {
|
||||
currencyCode = settings.currency
|
||||
inputMode = InputMode(rawValue: settings.inputMode) ?? .simple
|
||||
|
||||
// Load statistics
|
||||
totalSources = sourceRepository.sourceCount
|
||||
totalCategories = categoryRepository.categories.count
|
||||
totalSnapshots = sourceRepository.sources.reduce(0) { $0 + $1.snapshotCount }
|
||||
// Load statistics directly from database to avoid async race conditions
|
||||
loadStatistics()
|
||||
|
||||
FirebaseService.shared.logScreenView(screenName: "Settings")
|
||||
}
|
||||
|
||||
private func loadStatistics() {
|
||||
let context = CoreDataStack.shared.viewContext
|
||||
|
||||
// Fetch source count directly
|
||||
let sourceRequest: NSFetchRequest<InvestmentSource> = InvestmentSource.fetchRequest()
|
||||
totalSources = (try? context.count(for: sourceRequest)) ?? 0
|
||||
|
||||
// Fetch category count directly
|
||||
let categoryRequest: NSFetchRequest<Category> = Category.fetchRequest()
|
||||
totalCategories = (try? context.count(for: categoryRequest)) ?? 0
|
||||
|
||||
// Fetch snapshot count directly
|
||||
let snapshotRequest: NSFetchRequest<Snapshot> = Snapshot.fetchRequest()
|
||||
totalSnapshots = (try? context.count(for: snapshotRequest)) ?? 0
|
||||
}
|
||||
|
||||
// MARK: - Premium Actions
|
||||
|
||||
func upgradeToPremium() {
|
||||
@@ -142,6 +156,16 @@ class SettingsViewModel: ObservableObject {
|
||||
|
||||
// MARK: - Export
|
||||
|
||||
@Published var isExporting = false
|
||||
@Published var exportProgress: Double = 0
|
||||
@Published var exportStatus = ""
|
||||
@Published var shareItem: ShareItem?
|
||||
|
||||
struct ShareItem: Identifiable {
|
||||
let id = UUID()
|
||||
let url: URL
|
||||
}
|
||||
|
||||
func exportData(format: ExportService.ExportFormat) {
|
||||
guard freemiumValidator.canExport() else {
|
||||
showingPaywall = true
|
||||
@@ -149,19 +173,79 @@ class SettingsViewModel: ObservableObject {
|
||||
return
|
||||
}
|
||||
|
||||
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
||||
let viewController = windowScene.windows.first?.rootViewController else {
|
||||
return
|
||||
}
|
||||
isExporting = true
|
||||
exportProgress = 0
|
||||
exportStatus = "Preparing export..."
|
||||
|
||||
ExportService.shared.share(
|
||||
format: format,
|
||||
sources: sourceRepository.sources,
|
||||
categories: categoryRepository.categories,
|
||||
from: viewController
|
||||
)
|
||||
Task {
|
||||
// Fetch data directly from database
|
||||
let context = CoreDataStack.shared.viewContext
|
||||
let sources = fetchAllSources(in: context)
|
||||
let categories = fetchAllCategories(in: context)
|
||||
|
||||
await MainActor.run {
|
||||
exportProgress = 0.3
|
||||
exportStatus = "Generating \(format.rawValue) file..."
|
||||
}
|
||||
|
||||
let content: String
|
||||
let fileName: String
|
||||
|
||||
switch format {
|
||||
case .csv:
|
||||
content = ExportService.shared.exportToCSV(sources: sources, categories: categories)
|
||||
fileName = "investment_tracker_export.csv"
|
||||
case .json:
|
||||
content = ExportService.shared.exportToJSON(sources: sources, categories: categories)
|
||||
fileName = "investment_tracker_export.json"
|
||||
}
|
||||
|
||||
await MainActor.run {
|
||||
exportProgress = 0.7
|
||||
exportStatus = "Saving file..."
|
||||
}
|
||||
|
||||
// Create temporary file
|
||||
let tempURL = FileManager.default.temporaryDirectory
|
||||
.appendingPathComponent(fileName)
|
||||
|
||||
do {
|
||||
try content.write(to: tempURL, atomically: true, encoding: .utf8)
|
||||
|
||||
await MainActor.run {
|
||||
exportProgress = 1.0
|
||||
exportStatus = "Export complete"
|
||||
isExporting = false
|
||||
showingExportOptions = false
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
|
||||
self?.shareItem = ShareItem(url: tempURL)
|
||||
}
|
||||
FirebaseService.shared.logExportAttempt(format: format.rawValue, success: true)
|
||||
}
|
||||
} catch {
|
||||
await MainActor.run {
|
||||
isExporting = false
|
||||
errorMessage = "Export failed: \(error.localizedDescription)"
|
||||
FirebaseService.shared.logExportAttempt(format: format.rawValue, success: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func fetchAllSources(in context: NSManagedObjectContext) -> [InvestmentSource] {
|
||||
let request: NSFetchRequest<InvestmentSource> = InvestmentSource.fetchRequest()
|
||||
request.sortDescriptors = [NSSortDescriptor(keyPath: \InvestmentSource.name, ascending: true)]
|
||||
return (try? context.fetch(request)) ?? []
|
||||
}
|
||||
|
||||
private func fetchAllCategories(in context: NSManagedObjectContext) -> [Category] {
|
||||
let request: NSFetchRequest<Category> = Category.fetchRequest()
|
||||
request.sortDescriptors = [NSSortDescriptor(keyPath: \Category.name, ascending: true)]
|
||||
return (try? context.fetch(request)) ?? []
|
||||
}
|
||||
|
||||
// Share sheet is presented in SettingsView using shareItem.
|
||||
|
||||
var canExport: Bool {
|
||||
freemiumValidator.canExport()
|
||||
}
|
||||
@@ -202,41 +286,79 @@ class SettingsViewModel: ObservableObject {
|
||||
// MARK: - Data Management
|
||||
|
||||
func resetAllData() {
|
||||
isLoading = true
|
||||
errorMessage = nil
|
||||
|
||||
let context = CoreDataStack.shared.viewContext
|
||||
let entityNames = [
|
||||
"Snapshot",
|
||||
"InvestmentSource",
|
||||
"Category",
|
||||
"Asset",
|
||||
"Account",
|
||||
"Goal",
|
||||
"Transaction",
|
||||
"PredictionCache"
|
||||
]
|
||||
|
||||
// Delete all snapshots, sources, and categories
|
||||
for source in sourceRepository.sources {
|
||||
context.delete(source)
|
||||
}
|
||||
for category in categoryRepository.categories {
|
||||
context.delete(category)
|
||||
}
|
||||
let assetRequest: NSFetchRequest<Asset> = Asset.fetchRequest()
|
||||
let accountRequest: NSFetchRequest<Account> = Account.fetchRequest()
|
||||
let goalRequest: NSFetchRequest<Goal> = Goal.fetchRequest()
|
||||
if let assets = try? context.fetch(assetRequest) {
|
||||
assets.forEach { context.delete($0) }
|
||||
}
|
||||
if let accounts = try? context.fetch(accountRequest) {
|
||||
accounts.forEach { context.delete($0) }
|
||||
}
|
||||
if let goals = try? context.fetch(goalRequest) {
|
||||
goals.forEach { context.delete($0) }
|
||||
context.performAndWait {
|
||||
var deletedObjectIDs: [NSManagedObjectID] = []
|
||||
for name in entityNames {
|
||||
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: name)
|
||||
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
|
||||
deleteRequest.resultType = .resultTypeObjectIDs
|
||||
if let result = try? context.execute(deleteRequest) as? NSBatchDeleteResult,
|
||||
let objectIDs = result.result as? [NSManagedObjectID] {
|
||||
deletedObjectIDs.append(contentsOf: objectIDs)
|
||||
}
|
||||
}
|
||||
|
||||
if !deletedObjectIDs.isEmpty {
|
||||
NSManagedObjectContext.mergeChanges(
|
||||
fromRemoteContextSave: [NSDeletedObjectsKey: deletedObjectIDs],
|
||||
into: [context]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
CoreDataStack.shared.save()
|
||||
context.reset()
|
||||
|
||||
// Clear notifications
|
||||
context.performAndWait {
|
||||
// Recreate default categories
|
||||
let categoryCount = (try? context.count(for: Category.fetchRequest())) ?? 0
|
||||
if categoryCount == 0 {
|
||||
Category.createDefaultCategories(in: context)
|
||||
}
|
||||
|
||||
// Recreate default account
|
||||
let accountCount = (try? context.count(for: Account.fetchRequest())) ?? 0
|
||||
if accountCount == 0 {
|
||||
let defaultCurrency = AppSettings.getOrCreate(in: context).currency
|
||||
let account = Account(context: context)
|
||||
account.name = Account.defaultAccountName
|
||||
account.currency = defaultCurrency
|
||||
account.inputMode = InputMode.simple.rawValue
|
||||
account.notificationFrequency = NotificationFrequency.monthly.rawValue
|
||||
account.customFrequencyMonths = 1
|
||||
account.sortOrder = 0
|
||||
}
|
||||
|
||||
let settings = AppSettings.getOrCreate(in: context)
|
||||
settings.selectedAccountId = nil
|
||||
settings.showAllAccounts = true
|
||||
|
||||
try? context.save()
|
||||
}
|
||||
|
||||
// Clear notifications + monthly check-ins
|
||||
notificationService.cancelAllReminders()
|
||||
MonthlyCheckInStore.clearAll()
|
||||
CoreDataStack.shared.refreshWidgetData()
|
||||
|
||||
// Recreate default categories
|
||||
categoryRepository.createDefaultCategoriesIfNeeded()
|
||||
_ = AccountRepository().createDefaultAccountIfNeeded()
|
||||
|
||||
// Reload data
|
||||
loadSettings()
|
||||
|
||||
isLoading = false
|
||||
successMessage = "All data has been reset"
|
||||
NotificationCenter.default.post(name: .didResetData, object: nil)
|
||||
}
|
||||
|
||||
// MARK: - Computed Properties
|
||||
|
||||
Reference in New Issue
Block a user