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 }