BackupView.swift (6085B)
1 /* 2 * This file is part of GNU Taler, ©2022-26 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 created: Bool = false 27 @State private var restored: Bool = false 28 @State private var backups: [String] = [] 29 @State private var filename: String = EMPTYSTRING 30 @State private var selectedBackup: String? = nil 31 @State private var showRestoreAlert: Bool = false 32 33 private func restoreBackup(_ path: String) { 34 restored = true 35 Task { 36 try? await model.importDbFromFile(path: path) 37 } 38 } 39 40 private func createBackup() { 41 created = true 42 Task { 43 let dateString = Date().iso 44 let stem = String(BACKUP + dateString) 45 if let backupFile = try? await model.exportDbToFile(stem: stem) { 46 filename = backupFile 47 await viewDidLoad() 48 } 49 } 50 } 51 52 private var dismissAlertButton: some View { 53 Button("Cancel", role: .cancel) { 54 showRestoreAlert = false 55 withAnimation { selectedBackup = nil } 56 } 57 } 58 private var resetButton: some View { 59 Button("Restore", role: .destructive) { // TODO: WalletColors().errorColor 60 // didReset = true 61 62 if let selectedBackup, let docDirUrl = URL.docDirUrl { 63 showRestoreAlert = false 64 // symLog.log("❗️Restore \(selectedBackup)❗️") 65 // if #available(iOS 16.0, *) { 66 // backupPath = docDirUrl.appending(component: selectedBackup) 67 // } 68 let backupPath = docDirUrl.appendingPathComponent(selectedBackup).absoluteString 69 let path = backupPath.deletingPrefix(PREFIX) 70 symLog.log("❗️Restore \(path)❗️") 71 restoreBackup(path) 72 withAnimation { self.selectedBackup = nil } 73 } 74 } 75 } 76 77 @MainActor 78 private func viewDidLoad() async { 79 let fm = FileManager.default 80 if let docDirUrl = URL.docDirUrl { 81 if let contentsOfDocDir = try? fm.contentsOfDirectory(at: docDirUrl, 82 includingPropertiesForKeys: nil, 83 options: []) { 84 let filenames = contentsOfDocDir.map { $0.lastPathComponent } 85 backups = filenames.filter { $0.hasPrefix(BACKUP) }.sorted(by: >) 86 } 87 } 88 } 89 90 var body: some View { 91 #if PRINT_CHANGES 92 let _ = Self._printChanges() 93 let _ = symLog.vlog() // just to get the # to compare it with .onAppear & onDisappear 94 #endif 95 let buttonTitle = String(localized: "Create Backup", comment: "button") 96 97 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.") 98 99 let restoreHint = Text("To restore your digital money, tap on the backup you want to restore from the list below.") 100 101 let restoreHint2 = Text("To restore on a different iPhone, first install the Taler Wallet app there. Then connect it to your computer, and use the Files dialog to copy a previously saved backup into the Taler Wallet. Finally open the Backup/Restore dialog there, and select the backup you just copied.") 102 let hasBackups = !backups.isEmpty 103 List { 104 Section { 105 backupHint 106 .listRowSeparator(.hidden) 107 FeedbackButton(buttonTitle, disabled: created) { createBackup() } 108 .padding(.horizontal) 109 .listRowSeparator(.hidden) 110 restoreHint 111 restoreHint2 112 } 113 .talerFont(.body) 114 115 Section { 116 if hasBackups { 117 ForEach(backups, id: \.self) { file in 118 let isSelected = file == selectedBackup 119 HStack { 120 Text(file) 121 .padding(.vertical, 2) 122 Spacer() 123 } 124 .background { 125 Color.primary.opacity(isSelected ? 0.12 : 0.001) 126 } 127 .onTapGesture { 128 withAnimation { selectedBackup = file } 129 showRestoreAlert = true 130 } 131 } 132 } else { 133 Text("No backups yet...") 134 .talerFont(.body) 135 } 136 } header: { 137 Text("Available for restore:") 138 .talerFont(.title3) 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_BACKUP, stack: stack.push()) 156 } 157 } // body 158 }