taler-ios

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

MigrationView.swift (8175B)


      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 SymLog
     10 import taler_swift
     11 
     12 struct MigrationView: View {
     13 
     14     @Environment(\.colorScheme) private var colorScheme
     15     @Environment(\.colorSchemeContrast) private var colorSchemeContrast
     16     @EnvironmentObject private var controller: Controller
     17     @EnvironmentObject private var model: WalletModel
     18 #if DEBUG
     19     @AppStorage("developerMode") var developerMode: Bool = true
     20 #else
     21     @AppStorage("developerMode") var developerMode: Bool = false
     22 #endif
     23     @AppStorage("preferredColorScheme") var preferredColorScheme: Int = 0
     24     @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
     25 
     26     @State private var isMigrating: Bool = false
     27     @State private var fakeMigration: Bool = false
     28     @State private var isFinished: Bool = false
     29     @State private var isCancelled: Bool = false
     30     @State private var progress: Double = 0
     31     @State private var error2: Error? = nil
     32     @State private var showTechHint: Bool = false
     33 
     34     func chevronAction() {
     35         withAnimation {
     36             showTechHint.toggle()
     37         }
     38     }
     39     func buttonAction() {
     40         if !isMigrating {
     41             withAnimation {
     42                 self.isMigrating = true
     43             }
     44             Task {
     45                 let result = try? await model.migrateDatabase()
     46                 if let migrated = result?.migrated {
     47                     withAnimation {
     48                         isFinished = migrated
     49                         self.progress = 1
     50                     }
     51                 }
     52             }
     53         }
     54     }
     55 
     56     func fakeAction() {
     57         if !isMigrating {
     58             fakeMigration = true
     59             withAnimation {
     60                 self.isMigrating = true
     61             }
     62         }
     63     }
     64 
     65     // Too much hassle for wallet-core to check this everywhere
     66 //    func cancelAction() {
     67 //        self.isMigrating = false
     68 //        self.isCancelled = true
     69 //        if let operation = controller.progressOperation,
     70 //           let token = controller.progressToken {
     71 //            Task {
     72 //                try? await model.cancelProgressToken(operation, token: token)
     73 //                controller.progressOperation = nil
     74 //                controller.progressToken = nil
     75 //            }
     76 //        }
     77 //    }
     78 
     79     func finishAction() {
     80         withAnimation {
     81             controller.backendState = .ready
     82         }
     83     }
     84 
     85     var body: some View {
     86         let list = List {
     87             Section(header: Text("Update database")) {
     88                 if isMigrating || isFinished || isCancelled {
     89                     if fakeMigration {
     90                         Text("Simulating update:")
     91                             .talerFont(.title)
     92                             .task {
     93                                 for x in 0...90 {
     94                                     await Task.sleep(20_000_000)
     95                                     self.progress = Double(x) / 100
     96                                 }
     97                                 withAnimation {
     98                                     isFinished = true
     99                                     self.progress = 1
    100                                 }
    101                                 controller.hapticFeedback(.medium)
    102                             }
    103                     } else {
    104                         Text("Updating the database:")
    105                             .talerFont(.title)
    106                     }
    107                     ProgressView(value: progress)
    108 //                    Button("Cancel", action: cancelAction)
    109 //                        .buttonStyle(TalerButtonStyle(type: .bordered))
    110 //                        .padding()
    111                     if isFinished || isCancelled {
    112                         Text(isCancelled ? "Cancelled" : "Finished")
    113                             .talerFont(.title)
    114                             .task {
    115                                 DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
    116                                     finishAction()
    117                                 }
    118                             }
    119                     } else {
    120                         Text("Please don't close the app now, or we need to start over next time.")
    121                             .talerFont(.body)
    122                     }
    123                 } else {
    124                     let device = controller.isIpad ? Text("this iPad", comment: "device")
    125                                                    : Text("this iPhone", comment: "device")
    126                     Text("The \(controller.localizedAppName) database on \(device) can be updated and will then be faster.", comment: "appName, device")
    127                         .talerFont(.title)
    128                     Text("This might take from a few seconds to a minute or two, depending on how many transactions you made.")
    129                         .talerFont(.body)
    130 
    131                     Button("Update now", action: buttonAction)
    132                         .buttonStyle(TalerButtonStyle(type: .bordered, disabled: isMigrating))
    133                         .disabled(isMigrating)
    134                         .padding()
    135 #if TALER_NIGHTLY || GNU_TALER
    136                     if developerMode {
    137                         Button("Simulate update", action: fakeAction)
    138                             .buttonStyle(TalerButtonStyle(type: .bordered, disabled: isMigrating))
    139                             .disabled(isMigrating)
    140                             .padding()
    141                     }
    142 #endif
    143                     Button("Update later", action: finishAction)
    144                         .buttonStyle(TalerButtonStyle(type: .bordered))
    145                         .padding()
    146 
    147                     Button(action: chevronAction) {
    148                         let chevron = Image(systemName: CHEVRON_UP)     // 􀆇
    149                         HStack(alignment: .firstTextBaseline) {
    150                                 Text("Technical information")
    151                                     .talerFont(.body)
    152                             Spacer()
    153                             chevron.rotationEffect(.degrees((showTechHint ?  -180 : 0)))
    154                                 .accessibilityHidden(true)
    155                         }
    156                         .accessibilityElement(children: .combine)
    157                         .accessibilityHint(Text(showTechHint ? "Hide Technical information"
    158                                                              : "Show Technical information"))
    159                         .contentShape(.rect)    // can tap in background also
    160                     }   .buttonStyle(.plain)
    161                     if showTechHint {
    162                         Text("The database, which holds your money and your transactions, is updated from \"indexed\" to \"sqlite3\" format.\nThe app cannot be used during that time.")
    163                             .talerFont(.body)
    164                     }
    165                 }
    166             }
    167             .listRowSeparator(.hidden)
    168         }.id("list")
    169         .listStyle(myListStyle.style).anyView
    170         .talerFont(.title2)
    171         .onNotification(.DatabaseMaintenance) { notification in
    172             if let payload = notification.userInfo?[NOTIFICATIONPAYLOAD] as? WalletCore.Payload {
    173                 if let error = payload.error {
    174                     self.error2 = WalletBackendError.walletCoreError(error)
    175                 } else if let percent = payload.completionPercent {
    176                     withAnimation {
    177                         progress = Double(percent) / 100
    178                     }
    179                 }
    180             }
    181         }
    182         let stack = ZStack {
    183             if preferredColorScheme == 3, #available(iOS 17.0, *) {
    184                 list.scrollContentBackground(.hidden)
    185             } else {
    186                 list
    187             }
    188             if let error2 {
    189                 ErrorView(nil, error: error2, devMode: developerMode) {
    190                     self.error2 = nil
    191                 }.interactiveDismissDisabled()
    192             }
    193         }.id("stack")
    194         NavigationView {
    195             ZStack {
    196                 WalletHeader(showSettings: false, content: stack)
    197             }.frame(maxWidth: .infinity, maxHeight: .infinity)
    198                 .background(FullBackground())
    199         }.navigationViewStyle(.stack)
    200     }
    201 }