BackupView.swift (6034B)
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 = EMPTYSTRING 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 await viewDidLoad() 50 } 51 } 52 } 53 54 private var dismissAlertButton: some View { 55 Button("Cancel", role: .cancel) { 56 showRestoreAlert = false 57 withAnimation { selectedBackup = nil } 58 } 59 } 60 private var resetButton: some View { 61 Button("Restore", role: .destructive) { // TODO: WalletColors().errorColor 62 // didReset = true 63 64 if let selectedBackup, let docDirUrl = URL.docDirUrl { 65 showRestoreAlert = false 66 // symLog.log("❗️Restore \(selectedBackup)❗️") 67 // if #available(iOS 16.0, *) { 68 // backupPath = docDirUrl.appending(component: selectedBackup) 69 // } 70 let backupPath = docDirUrl.appendingPathComponent(selectedBackup).absoluteString 71 let path = backupPath.deletingPrefix(PREFIX) 72 symLog.log("❗️Restore \(path)❗️") 73 restoreBackup(path) 74 withAnimation { self.selectedBackup = nil } 75 } 76 } 77 } 78 79 @MainActor 80 private func viewDidLoad() async { 81 let fm = FileManager.default 82 if let docDirUrl = URL.docDirUrl { 83 if let contentsOfDocDir = try? fm.contentsOfDirectory(at: docDirUrl, 84 includingPropertiesForKeys: nil, 85 options: []) { 86 let filenames = contentsOfDocDir.map { $0.lastPathComponent } 87 withAnimation { files = filenames } 88 } 89 } 90 } 91 92 var body: some View { 93 #if PRINT_CHANGES 94 let _ = Self._printChanges() 95 let _ = symLog.vlog() // just to get the # to compare it with .onAppear & onDisappear 96 #endif 97 let a11yLabelStr = String(localized: "Add bank account", comment: "a11y for the + button") 98 let addTitleStr = String(localized: "Add bank account", comment: "title of the addExchange alert") 99 let buttonTitle = String(localized: "Create Backup", comment: "button") 100 101 102 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.") 103 104 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.") 105 106 let backups = files.filter { $0.hasPrefix(BACKUP) } 107 let hasBackups = !backups.isEmpty 108 List { 109 Section { 110 backupHint 111 .listRowSeparator(.hidden) 112 FeedbackButton(buttonTitle, disabled: created) { createBackup() } 113 .padding() 114 .listRowSeparator(.hidden) 115 restoreHint 116 } 117 if hasBackups { 118 Section { 119 ForEach(backups, id: \.self) { file in 120 let isSelected = file == selectedBackup 121 HStack { 122 Text(file) 123 .padding(.vertical, 2) 124 Spacer() 125 } 126 .background { 127 Color.primary.opacity(isSelected ? 0.2 : 0.05) 128 } 129 .onTapGesture { 130 withAnimation { selectedBackup = file } 131 showRestoreAlert = true 132 } 133 } 134 } header: { 135 Text("Available for restore:") 136 .talerFont(.title3) 137 } 138 } 139 } 140 .listStyle(myListStyle.style).anyView 141 .task { await viewDidLoad() } 142 .navigationTitle(navTitle) 143 .onChange(of: selectedBackup) { selected in 144 if selected != nil { 145 showRestoreAlert = true 146 } 147 } 148 .alert("Overwrite Wallet", 149 isPresented: $showRestoreAlert, 150 actions: { dismissAlertButton 151 resetButton }, 152 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.") }) 153 .onAppear() { 154 DebugViewC.shared.setViewID(VIEW_BANK_ACCOUNTS, stack: stack.push()) 155 } 156 } // body 157 }