taler-ios

iOS apps for GNU Taler (wallet)
Log | Files | Refs | README | LICENSE

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 }