BackupView.swift (6069B)
1 /* 2 * This file is part of GNU Taler, ©2022-25 Taler Systems S.A. 3 * See LICENSE.md 4 */ 5 /** 6 * @author Marc Stibane 7 */ 8 import SwiftUI 9 import taler_swift 10 import SymLog 11 12 let BACKUP = "Taler-" 13 let PREFIX = "file://" 14 15 /// This view shows the list of backups 16 struct BackupView: View { 17 private let symLog = SymLogV(0) 18 let stack: CallStack 19 let navTitle: String 20 21 @EnvironmentObject private var model: WalletModel 22 @EnvironmentObject private var controller: Controller 23 @AppStorage("minimalistic") var minimalistic: Bool = false 24 @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic 25 26 @State private var amountLastUsed = Amount.zero(currency: EMPTYSTRING) // needed for Deposit, ignore 27 @State private var created: Bool = false 28 @State private var restored: Bool = false 29 @State private var files: [String] = [] 30 @State private var filename: String = "" 31 @State private var selectedBackup: String? = nil 32 @State private var showRestoreAlert: Bool = false 33 34 35 private func restoreBackup(_ path: String) { 36 restored = true 37 Task { 38 try? await model.importDbFromFile(path: path) 39 } 40 } 41 42 private func createBackup() { 43 created = true 44 Task { 45 let dateString = Date().iso 46 let stem = String(BACKUP + dateString) 47 if let backupFile = try? await model.exportDbToFile(stem: stem) { 48 filename = backupFile 49 } 50 } 51 } 52 53 private var dismissAlertButton: some View { 54 Button("Cancel", role: .cancel) { 55 showRestoreAlert = false 56 withAnimation { selectedBackup = nil } 57 } 58 } 59 private var resetButton: some View { 60 Button("Restore", role: .destructive) { // TODO: WalletColors().errorColor 61 // didReset = true 62 63 if let selectedBackup, let docDirUrl = URL.docDirUrl { 64 showRestoreAlert = false 65 // symLog.log("❗️Restore \(selectedBackup)❗️") 66 // if #available(iOS 16.0, *) { 67 // backupPath = docDirUrl.appending(component: selectedBackup) 68 // } 69 let backupPath = docDirUrl.appendingPathComponent(selectedBackup).absoluteString 70 let path = backupPath.deletingPrefix(PREFIX) 71 symLog.log("❗️Restore \(path)❗️") 72 restoreBackup(path) 73 withAnimation { self.selectedBackup = nil } 74 } 75 } 76 } 77 78 @MainActor 79 private func viewDidLoad() async { 80 let fm = FileManager.default 81 if let docDirUrl = URL.docDirUrl { 82 if let contentsOfDocDir = try? fm.contentsOfDirectory(at: docDirUrl, 83 includingPropertiesForKeys: nil, 84 options: []) { 85 let filenames = contentsOfDocDir.map { $0.lastPathComponent } 86 withAnimation { files = filenames } 87 } 88 } 89 } 90 91 var body: some View { 92 #if PRINT_CHANGES 93 let _ = Self._printChanges() 94 let _ = symLog.vlog() // just to get the # to compare it with .onAppear & onDisappear 95 #endif 96 let a11yLabelStr = String(localized: "Add bank account", comment: "a11y for the + button") 97 let addTitleStr = String(localized: "Add bank account", comment: "title of the addExchange alert") 98 let buttonTitle = String(localized: "Create Backup", comment: "button") 99 100 101 let backupHint = Text("Tap 'Create Backup' to make a copy of your digital money. Connect your iPhone to a computer, then use the Files dialog and copy that backup to the computer.") 102 103 let restoreHint = Text("To restore your digital money, connect your iPhone to your computer, then use the Files dialog and copy a previously saved backup from your computer into the Taler Wallet.") 104 105 let backups = files.filter { $0.hasPrefix(BACKUP) } 106 let hasBackups = !backups.isEmpty 107 List { 108 Section { 109 backupHint 110 .listRowSeparator(.hidden) 111 Button(buttonTitle) { createBackup() } 112 .buttonStyle(TalerButtonStyle(type: .bordered)) 113 .padding() 114 .listRowSeparator(.hidden) 115 .disabled(created) 116 restoreHint 117 } 118 if hasBackups { 119 Section { 120 ForEach(backups, id: \.self) { file in 121 let isSelected = file == selectedBackup 122 HStack { 123 Text(file) 124 .padding(.vertical, 2) 125 Spacer() 126 } 127 .background { 128 Color.primary.opacity(isSelected ? 0.2 : 0.05) 129 } 130 .onTapGesture { 131 withAnimation { selectedBackup = file } 132 showRestoreAlert = true 133 } 134 } 135 } header: { 136 Text("Available for restore:") 137 .talerFont(.title3) 138 } 139 } 140 } 141 .listStyle(myListStyle.style).anyView 142 .task { await viewDidLoad() } 143 .navigationTitle(navTitle) 144 .onChange(of: selectedBackup) { selected in 145 if selected != nil { 146 showRestoreAlert = true 147 } 148 } 149 .alert("Overwrite Wallet", 150 isPresented: $showRestoreAlert, 151 actions: { dismissAlertButton 152 resetButton }, 153 message: { Text("Are you sure you want to overwrite your wallet with this backup?\nThis cannot be reverted, all money which is now still in your wallet will be lost.") }) 154 .onAppear() { 155 DebugViewC.shared.setViewID(VIEW_BANK_ACCOUNTS, stack: stack.push()) 156 } 157 } // body 158 }