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 }