Add premium backups with retention and iCloud support
This commit is contained in:
@@ -20,6 +20,7 @@ struct SettingsView: View {
|
||||
@State private var showingPinDisableAlert = false
|
||||
@State private var showingRestartAlert = false
|
||||
@State private var didLoadCloudSync = false
|
||||
@State private var backupToRestore: BackupRecord?
|
||||
|
||||
init(iapService: IAPService) {
|
||||
self.iapService = iapService
|
||||
@@ -41,6 +42,9 @@ struct SettingsView: View {
|
||||
|
||||
// Data Section
|
||||
dataSection
|
||||
if viewModel.backupsEnabled {
|
||||
backupsSection
|
||||
}
|
||||
|
||||
// Security Section
|
||||
securitySection
|
||||
@@ -86,6 +90,26 @@ struct SettingsView: View {
|
||||
} message: {
|
||||
Text("This will permanently delete all your investment data. This action cannot be undone.")
|
||||
}
|
||||
.confirmationDialog(
|
||||
"Restore Backup",
|
||||
isPresented: Binding(
|
||||
get: { backupToRestore != nil },
|
||||
set: { if !$0 { backupToRestore = nil } }
|
||||
),
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Restore", role: .destructive) {
|
||||
if let backup = backupToRestore {
|
||||
viewModel.restoreBackup(backup)
|
||||
backupToRestore = nil
|
||||
}
|
||||
}
|
||||
Button("Cancel", role: .cancel) {
|
||||
backupToRestore = nil
|
||||
}
|
||||
} message: {
|
||||
Text("This will replace your current data with the selected backup.")
|
||||
}
|
||||
.alert("Success", isPresented: .constant(viewModel.successMessage != nil)) {
|
||||
Button("OK") {
|
||||
viewModel.successMessage = nil
|
||||
@@ -141,6 +165,9 @@ struct SettingsView: View {
|
||||
}
|
||||
.onAppear {
|
||||
didLoadCloudSync = true
|
||||
if viewModel.backupsEnabled {
|
||||
viewModel.refreshBackups()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -303,8 +330,25 @@ struct SettingsView: View {
|
||||
if didLoadCloudSync {
|
||||
showingRestartAlert = true
|
||||
}
|
||||
viewModel.refreshBackups()
|
||||
}
|
||||
|
||||
Toggle(
|
||||
isOn: Binding(
|
||||
get: { viewModel.backupsEnabled },
|
||||
set: { viewModel.setBackupsEnabled($0) }
|
||||
)
|
||||
) {
|
||||
HStack(spacing: 8) {
|
||||
Text("Enable Backups")
|
||||
if !viewModel.isPremium {
|
||||
Text("Premium")
|
||||
.font(.caption.weight(.semibold))
|
||||
.foregroundColor(.appWarning)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
if viewModel.canExport {
|
||||
viewModel.showingExportOptions = true
|
||||
@@ -359,6 +403,66 @@ struct SettingsView: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Backups Section
|
||||
|
||||
private var backupsSection: some View {
|
||||
Section {
|
||||
Picker("Keep Backups", selection: $viewModel.backupRetentionCount) {
|
||||
Text("5").tag(5)
|
||||
Text("10").tag(10)
|
||||
Text("20").tag(20)
|
||||
}
|
||||
.onChange(of: viewModel.backupRetentionCount) { _, newValue in
|
||||
viewModel.updateBackupRetention(newValue)
|
||||
}
|
||||
|
||||
Button {
|
||||
viewModel.createBackupNow()
|
||||
} label: {
|
||||
HStack {
|
||||
Label("Create Backup Now", systemImage: "arrow.clockwise")
|
||||
Spacer()
|
||||
if viewModel.isBackupInProgress {
|
||||
ProgressView()
|
||||
}
|
||||
}
|
||||
}
|
||||
.disabled(viewModel.isBackupInProgress || viewModel.isRestoreInProgress)
|
||||
|
||||
if viewModel.backups.isEmpty {
|
||||
Text("No backups yet.")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
} else {
|
||||
ForEach(viewModel.backups) { backup in
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(backup.date.formatted(date: .abbreviated, time: .shortened))
|
||||
.font(.subheadline)
|
||||
Text("\(backup.location.rawValue) · \(formatBytes(backup.size))")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
Spacer()
|
||||
Button("Restore") {
|
||||
backupToRestore = backup
|
||||
}
|
||||
.disabled(viewModel.isRestoreInProgress)
|
||||
}
|
||||
.contextMenu {
|
||||
Button("Share Backup") {
|
||||
viewModel.shareItem = SettingsViewModel.ShareItem(url: backup.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text("Backups")
|
||||
} footer: {
|
||||
Text("Backups are stored locally and in iCloud (when enabled).")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Security Section
|
||||
|
||||
private var securitySection: some View {
|
||||
@@ -589,6 +693,12 @@ struct SettingsView: View {
|
||||
}
|
||||
return "Portfolio Journal"
|
||||
}
|
||||
|
||||
private func formatBytes(_ bytes: Int64) -> String {
|
||||
let formatter = ByteCountFormatter()
|
||||
formatter.countStyle = .file
|
||||
return formatter.string(fromByteCount: bytes)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Activity View
|
||||
|
||||
Reference in New Issue
Block a user