Add premium backups with retention and iCloud support
This commit is contained in:
@@ -20,6 +20,8 @@ class SettingsViewModel: ObservableObject {
|
||||
@Published var showingExportOptions = false
|
||||
@Published var showingImportSheet = false
|
||||
@Published var showingResetConfirmation = false
|
||||
@Published var isBackupInProgress = false
|
||||
@Published var isRestoreInProgress = false
|
||||
@Published var errorMessage: String?
|
||||
@Published var successMessage: String?
|
||||
|
||||
@@ -29,6 +31,12 @@ class SettingsViewModel: ObservableObject {
|
||||
@Published var totalSnapshots = 0
|
||||
@Published var totalCategories = 0
|
||||
|
||||
// MARK: - Backups
|
||||
|
||||
@Published var backupRetentionCount = 10
|
||||
@Published var backups: [BackupRecord] = []
|
||||
@Published var backupsEnabled = false
|
||||
|
||||
// MARK: - Dependencies
|
||||
|
||||
private let iapService: IAPService
|
||||
@@ -37,6 +45,7 @@ class SettingsViewModel: ObservableObject {
|
||||
private let categoryRepository: CategoryRepository
|
||||
private let freemiumValidator: FreemiumValidator
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
private let backupsEnabledKey = "backupsEnabled"
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
@@ -61,7 +70,10 @@ class SettingsViewModel: ObservableObject {
|
||||
private func setupObservers() {
|
||||
iapService.$isPremium
|
||||
.receive(on: DispatchQueue.main)
|
||||
.assign(to: &$isPremium)
|
||||
.sink { [weak self] isPremium in
|
||||
self?.handlePremiumChange(isPremium)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
iapService.$isFamilyShared
|
||||
.receive(on: DispatchQueue.main)
|
||||
@@ -82,6 +94,13 @@ class SettingsViewModel: ObservableObject {
|
||||
analyticsEnabled = settings.enableAnalytics
|
||||
currencyCode = settings.currency
|
||||
inputMode = InputMode(rawValue: settings.inputMode) ?? .simple
|
||||
backupRetentionCount = loadBackupRetention()
|
||||
backupsEnabled = loadBackupsEnabled()
|
||||
if backupsEnabled {
|
||||
refreshBackups()
|
||||
} else {
|
||||
backups = []
|
||||
}
|
||||
|
||||
// Load statistics directly from database to avoid async race conditions
|
||||
loadStatistics()
|
||||
@@ -361,6 +380,85 @@ class SettingsViewModel: ObservableObject {
|
||||
NotificationCenter.default.post(name: .didResetData, object: nil)
|
||||
}
|
||||
|
||||
// MARK: - Backups
|
||||
|
||||
func updateBackupRetention(_ count: Int) {
|
||||
guard backupsEnabled, isPremium else { return }
|
||||
backupRetentionCount = count
|
||||
UserDefaults.standard.set(count, forKey: "backupRetentionCount")
|
||||
refreshBackups()
|
||||
}
|
||||
|
||||
func refreshBackups() {
|
||||
guard backupsEnabled, isPremium else { return }
|
||||
let includeICloud = UserDefaults.standard.bool(forKey: "cloudSyncEnabled")
|
||||
backups = BackupService.shared.listAllBackups(includeICloud: includeICloud)
|
||||
}
|
||||
|
||||
func createBackupNow() {
|
||||
guard backupsEnabled, isPremium else { return }
|
||||
guard !isBackupInProgress else { return }
|
||||
isBackupInProgress = true
|
||||
errorMessage = nil
|
||||
|
||||
Task {
|
||||
let includeICloud = UserDefaults.standard.bool(forKey: "cloudSyncEnabled")
|
||||
let records = BackupService.shared.createBackup(
|
||||
retentionCount: backupRetentionCount,
|
||||
includeICloud: includeICloud
|
||||
)
|
||||
await MainActor.run {
|
||||
backups = records
|
||||
isBackupInProgress = false
|
||||
successMessage = "Backup saved"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func restoreBackup(_ backup: BackupRecord) {
|
||||
guard backupsEnabled, isPremium else { return }
|
||||
guard !isRestoreInProgress else { return }
|
||||
isRestoreInProgress = true
|
||||
errorMessage = nil
|
||||
|
||||
Task {
|
||||
let content: String
|
||||
do {
|
||||
content = try String(contentsOf: backup.url, encoding: .utf8)
|
||||
} catch {
|
||||
await MainActor.run {
|
||||
isRestoreInProgress = false
|
||||
errorMessage = "Failed to read backup file."
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
await MainActor.run {
|
||||
resetAllData()
|
||||
}
|
||||
|
||||
let allowMultiple = iapService.isPremium
|
||||
let result = await ImportService.shared.importDataAsync(
|
||||
content: content,
|
||||
format: .json,
|
||||
allowMultipleAccounts: allowMultiple,
|
||||
defaultAccountName: Account.defaultAccountName,
|
||||
progress: { _ in }
|
||||
)
|
||||
|
||||
await MainActor.run {
|
||||
isRestoreInProgress = false
|
||||
if result.errors.isEmpty {
|
||||
successMessage = "Backup restored"
|
||||
} else {
|
||||
errorMessage = "Backup restored with warnings."
|
||||
}
|
||||
loadSettings()
|
||||
refreshBackups()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Computed Properties
|
||||
|
||||
var appVersion: String {
|
||||
@@ -405,4 +503,52 @@ class SettingsViewModel: ObservableObject {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
private func loadBackupRetention() -> Int {
|
||||
let value = UserDefaults.standard.integer(forKey: "backupRetentionCount")
|
||||
let options = [5, 10, 20]
|
||||
return options.contains(value) ? value : 10
|
||||
}
|
||||
|
||||
private func loadBackupsEnabled() -> Bool {
|
||||
let enabled = UserDefaults.standard.bool(forKey: backupsEnabledKey)
|
||||
if !isPremium && enabled {
|
||||
UserDefaults.standard.set(false, forKey: backupsEnabledKey)
|
||||
return false
|
||||
}
|
||||
return isPremium && enabled
|
||||
}
|
||||
|
||||
private func handlePremiumChange(_ isPremium: Bool) {
|
||||
self.isPremium = isPremium
|
||||
if isPremium {
|
||||
backupsEnabled = UserDefaults.standard.bool(forKey: backupsEnabledKey)
|
||||
if backupsEnabled {
|
||||
refreshBackups()
|
||||
}
|
||||
} else {
|
||||
if backupsEnabled {
|
||||
backupsEnabled = false
|
||||
}
|
||||
UserDefaults.standard.set(false, forKey: backupsEnabledKey)
|
||||
backups = []
|
||||
}
|
||||
}
|
||||
|
||||
func setBackupsEnabled(_ enabled: Bool) {
|
||||
guard isPremium else {
|
||||
backupsEnabled = false
|
||||
UserDefaults.standard.set(false, forKey: backupsEnabledKey)
|
||||
showingPaywall = true
|
||||
return
|
||||
}
|
||||
|
||||
backupsEnabled = enabled
|
||||
UserDefaults.standard.set(enabled, forKey: backupsEnabledKey)
|
||||
if enabled {
|
||||
refreshBackups()
|
||||
} else {
|
||||
backups = []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user