taler-ios

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

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 }