taler-ios

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

MigrationView.swift (8224B)


      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 isIpad : Bool { UIDevice.current.userInterfaceIdiom == .pad }
     86 
     87     var body: some View {
     88         let list = List {
     89             Section(header: Text("Update database")) {
     90                 if isMigrating || isFinished || isCancelled {
     91                     if fakeMigration {
     92                         Text("Simulating update:")
     93                             .talerFont(.title)
     94                             .task {
     95                                 for x in 0...90 {
     96                                     await Task.sleep(20_000_000)
     97                                     self.progress = Double(x) / 100
     98                                 }
     99                                 withAnimation {
    100                                     isFinished = true
    101                                     self.progress = 1
    102                                 }
    103                                 controller.hapticFeedback(.medium)
    104                             }
    105                     } else {
    106                         Text("Updating the database:")
    107                             .talerFont(.title)
    108                     }
    109                     ProgressView(value: progress)
    110 //                    Button("Cancel", action: cancelAction)
    111 //                        .buttonStyle(TalerButtonStyle(type: .bordered))
    112 //                        .padding()
    113                     if isFinished || isCancelled {
    114                         Text(isCancelled ? "Cancelled" : "Finished")
    115                             .talerFont(.title)
    116                             .task {
    117                                 DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
    118                                     finishAction()
    119                                 }
    120                             }
    121                     } else {
    122                         Text("Please don't close the app now, or we need to start over next time.")
    123                             .talerFont(.body)
    124                     }
    125                 } else {
    126                     let device = isIpad ? Text("this iPad", comment: "device")
    127                                         : Text("this iPhone", comment: "device")
    128                     Text("The \(controller.localizedAppName) database on \(device) can be updated and will then be faster.", comment: "appName, device")
    129                         .talerFont(.title)
    130                     Text("This might take from a few seconds to a minute or two, depending on how many transactions you made.")
    131                         .talerFont(.body)
    132 
    133                     Button("Update now", action: buttonAction)
    134                         .buttonStyle(TalerButtonStyle(type: .bordered, disabled: isMigrating))
    135                         .disabled(isMigrating)
    136                         .padding()
    137 #if TALER_NIGHTLY || GNU_TALER
    138                     if developerMode {
    139                         Button("Simulate update", action: fakeAction)
    140                             .buttonStyle(TalerButtonStyle(type: .bordered, disabled: isMigrating))
    141                             .disabled(isMigrating)
    142                             .padding()
    143                     }
    144 #endif
    145                     Button("Update later", action: finishAction)
    146                         .buttonStyle(TalerButtonStyle(type: .bordered))
    147                         .padding()
    148 
    149                     Button(action: chevronAction) {
    150                         let chevron = Image(systemName: CHEVRON_UP)     // 􀆇
    151                         HStack(alignment: .firstTextBaseline) {
    152                                 Text("Technical information")
    153                                     .talerFont(.body)
    154                             Spacer()
    155                             chevron.rotationEffect(.degrees((showTechHint ?  -180 : 0)))
    156                                 .accessibilityHidden(true)
    157                         }
    158                         .accessibilityElement(children: .combine)
    159                         .accessibilityHint(Text(showTechHint ? "Hide Technical information"
    160                                                              : "Show Technical information"))
    161                         .contentShape(.rect)    // can tap in background also
    162                     }   .buttonStyle(.plain)
    163                     if showTechHint {
    164                         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.")
    165                             .talerFont(.body)
    166                     }
    167                 }
    168             }
    169             .listRowSeparator(.hidden)
    170         }.id("list")
    171         .listStyle(myListStyle.style).anyView
    172         .talerFont(.title2)
    173         .onNotification(.DatabaseMaintenance) { notification in
    174             if let payload = notification.userInfo?[NOTIFICATIONPAYLOAD] as? WalletCore.Payload {
    175                 if let error = payload.error {
    176                     self.error2 = WalletBackendError.walletCoreError(error)
    177                 } else if let percent = payload.completionPercent {
    178                     withAnimation {
    179                         progress = Double(percent) / 100
    180                     }
    181                 }
    182             }
    183         }
    184         let stack = ZStack {
    185             if preferredColorScheme == 3, #available(iOS 17.0, *) {
    186                 list.scrollContentBackground(.hidden)
    187             } else {
    188                 list
    189             }
    190             if let error2 {
    191                 ErrorView(nil, error: error2, devMode: developerMode) {
    192                     self.error2 = nil
    193                 }.interactiveDismissDisabled()
    194             }
    195         }.id("stack")
    196         NavigationView {
    197             ZStack {
    198                 WalletHeader(showSettings: false, content: stack)
    199             }.frame(maxWidth: .infinity, maxHeight: .infinity)
    200                 .background(FullBackground())
    201         }.navigationViewStyle(.stack)
    202     }
    203 }