LoadingView.swift (6726B)
1 /* 2 * This file is part of GNU Taler, ©2022-25 Taler Systems S.A. 3 * See LICENSE.md 4 */ 5 /** 6 * @author Marc Stibane 7 */ 8 import SwiftUI 9 import SymLog 10 11 struct LoadingView: View { 12 private let symLog = SymLogV(0) 13 let stack: CallStack 14 let scopeInfo: ScopeInfo? 15 let message: String? 16 17 // let backButtonHidden: Bool 18 let navTitle = String(localized: "Loading…") 19 20 @EnvironmentObject private var controller: Controller 21 @EnvironmentObject private var model: WalletModel 22 #if DEBUG 23 @AppStorage("developerMode") var developerMode: Bool = true 24 #else 25 @AppStorage("developerMode") var developerMode: Bool = false 26 #endif 27 28 @State private var rotationEnabled = true 29 @State private var showAlert: Bool = false 30 @State private var disabled: Bool = false 31 @State private var isFinished: Bool? = false 32 33 private var dismissButton: some View { 34 Button("Cancel", role: .cancel) { 35 showAlert = false 36 if let token = controller.progressToken { 37 controller.progressToken = nil 38 if let operation = controller.progressOperation { 39 controller.progressOperation = nil 40 // model.setError(nil) 41 Task { 42 try await model.cancelProgressToken(operation, token: token) 43 if !developerMode { 44 model.setError(nil) 45 } 46 } 47 } 48 if !developerMode { 49 dismissTop(stack.push()) 50 } 51 } else { 52 dismissTop(stack.push()) 53 } 54 } 55 .buttonStyle(TalerButtonStyle(type: .bordered, disabled: false)) 56 .padding(.horizontal) 57 } 58 59 @ViewBuilder 60 func retryButton(disabled: Bool) -> some View { 61 Button("Retry now") { 62 withAnimation { 63 showAlert = false 64 } 65 if let token = controller.progressToken { 66 if let operation = controller.progressOperation { 67 controller.lastProgressError = nil 68 Task { 69 try await model.retryProgressTokenNow(operation, token: token) 70 } 71 } 72 } 73 } 74 .buttonStyle(TalerButtonStyle(type: .prominent, disabled: disabled)) 75 .padding(.horizontal) 76 } 77 78 fileprivate func toJSON(_ lastError: RequestProgressError?) -> String? { 79 if let lastError { 80 do { 81 let jsonData = try JSONEncoder().encode(lastError) 82 if let jsonString = String(data: jsonData, encoding: .utf8) { 83 return jsonString 84 } 85 } catch { // JSON encoding of error failed / should never happen 86 return "Couldn't encode error" 87 } 88 } 89 return nil 90 } 91 92 var body: some View { 93 let view = VStack(alignment: .center) { 94 if showAlert { 95 let lastError = controller.lastProgressError 96 let fallback = String(localized: "It seems that there is a problem with the network.") 97 Text("Couldn't establish a connection") 98 .talerFont(.title2) 99 .padding(.vertical) 100 let hint = lastError?.error.hint 101 ?? lastError?.error.errorResponse?.hint 102 Text(hint ?? fallback) 103 .talerFont(.body) 104 if let jsonString = toJSON(lastError) { 105 CopyButton(jsonString, title: "Copy Details") 106 .padding(.vertical) 107 } 108 Spacer() 109 if let lastPhase = controller.lastProgressPhase { 110 if lastPhase.phase == .stalled { 111 if let lastError, let delay = lastError.nextRetryDelay { 112 if isFinished == false { 113 TimeView("Next retry in:", seconds: Int(delay.toSeconds()), isFinished: $isFinished) 114 .talerFont(.title3) 115 .padding(.vertical) 116 } else if isFinished == true { 117 Text("Retrying…") 118 .talerFont(.title3) 119 } else { 120 Text("Waiting for response…") 121 .talerFont(.title3) 122 } 123 } 124 // Spacer() 125 retryButton(disabled: isFinished ?? false) 126 } 127 } 128 } else { 129 Spacer() 130 RotatingTaler(size: 100, 131 progress: true, // VoiceOver "In progress" 132 once: false, 133 rotationEnabled: $rotationEnabled) 134 .onTapGesture(count: 1) { 135 rotationEnabled.toggle() 136 } 137 Spacer() 138 if let scopeInfo { 139 if let urlStr = scopeInfo.url { 140 Text(urlStr.trimURL) 141 .talerFont(.title2) 142 } else { 143 Text(scopeInfo.currency) 144 .talerFont(.title2) 145 } 146 Spacer() 147 } 148 Text(message ?? EMPTYSTRING) 149 .talerFont(.title2) 150 Spacer() 151 } 152 dismissButton 153 } 154 .frame(maxWidth: .infinity) 155 .padding() 156 .talerFont(.title) 157 .navigationTitle(navTitle) 158 .background(FullBackground()) 159 .onChange(of: controller.stalledConnection) { isStalled in 160 showAlert = isStalled 161 } 162 .onChange(of: controller.errorConnection) { isError in 163 if isError { 164 // print("❗️❗️❗️ error arrived, idle waiting starts") 165 isFinished = false 166 controller.errorConnection = false 167 } 168 } 169 170 if #available(iOS 17.0, *) { 171 view.toolbarTitleDisplayMode(.inlineLarge) 172 } else { 173 view 174 } 175 } 176 } 177 // MARK: - 178 struct LoadingView_Previews: PreviewProvider { 179 static var previews: some View { 180 NavigationView { 181 LoadingView(stack: CallStack("Loading"), scopeInfo: nil, message: "test message") // , backButtonHidden: true) 182 .navigationBarTitleDisplayMode(.automatic) 183 }.navigationViewStyle(.stack) 184 } 185 }