taler-ios

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

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 }