ErrorView.swift (5906B)
1 /* 2 * This file is part of GNU Taler, ©2022-26 Taler Systems S.A. 3 * See LICENSE.md 4 */ 5 /** 6 * @author Iván Ávalos 7 */ 8 import SwiftUI 9 import taler_swift 10 11 enum ErrorData { 12 case message(title: String, message: String) 13 case error(Error) 14 } 15 16 struct ErrorView: View { 17 let stack: CallStack 18 var title: String 19 var message: String? = nil 20 var requestUrl: String? = nil 21 var copyable: Bool 22 23 @State var isCopied: Bool = false 24 25 var onDismiss: () -> Void 26 27 init(_ stack: CallStack, title: String, message: String? = nil, 28 requestUrl: String? = nil, copyable: Bool, onDismiss: @escaping () -> Void) { 29 self.stack = stack 30 self.title = title 31 self.message = message 32 self.requestUrl = requestUrl 33 self.copyable = copyable 34 self.onDismiss = onDismiss 35 } 36 37 init(_ stack: CallStack, error: Error, devMode: Bool, onDismiss: @escaping () -> Void) { 38 let internetError = String(localized: "Network problem") 39 let internetMessage = String(localized: "Please check your internet connection and try again.") 40 let walletCoreError = String(localized: "Internal core error") 41 let initializationError = String(localized: "Initialization error") 42 let serializationError = String(localized: "Serialization error") 43 let deserializationError = String(localized: "Deserialization error") 44 let unknownError = String(localized: "Unknown error") 45 46 switch error { 47 case let walletError as WalletBackendError: 48 switch walletError { 49 case .walletCoreError(let walletBackendResponseError): 50 if let responseError = walletBackendResponseError { 51 if responseError.code == 7001 || responseError.code == 7005 { 52 self.init(stack, 53 title: internetError, 54 message: responseError.hint ?? internetMessage, 55 requestUrl: responseError.requestUrl, 56 copyable: false, 57 onDismiss: onDismiss) 58 break 59 } 60 } 61 if let json = walletBackendResponseError?.toJSON(), devMode { 62 self.init(stack, title: walletCoreError, message: json, copyable: true, onDismiss: onDismiss) 63 } else if let hint = walletBackendResponseError?.errorResponse?.hint ?? walletBackendResponseError?.hint { 64 self.init(stack, title: walletCoreError, message: hint, copyable: false, onDismiss: onDismiss) 65 } else if let message = walletBackendResponseError?.message { 66 self.init(stack, title: walletCoreError, message: message, copyable: false, onDismiss: onDismiss) 67 } else { 68 self.init(stack, title: walletCoreError, copyable: false, onDismiss: onDismiss) 69 } 70 case .initializationError: 71 self.init(stack, title: initializationError, copyable: false, onDismiss: onDismiss) 72 case .serializationError: 73 self.init(stack, title: serializationError, copyable: false, onDismiss: onDismiss) 74 case .deserializationError: 75 self.init(stack, title: deserializationError, copyable: false, onDismiss: onDismiss) 76 } 77 default: 78 self.init(stack, title: unknownError, message: error.localizedDescription, copyable: false, onDismiss: onDismiss) 79 } 80 } 81 82 init(_ stack: CallStack, data: ErrorData, devMode: Bool, onDismiss: @escaping () -> Void) { 83 switch data { 84 case .message(let title, let message): 85 self.init(stack, title: title, message: message, copyable: false, onDismiss: onDismiss) 86 case .error(let error): 87 self.init(stack, error: error, devMode: devMode, onDismiss: onDismiss) 88 } 89 } 90 91 var body: some View { 92 ScrollView { 93 VStack { 94 Image(systemName: EXCLAMATION) 95 .resizable() 96 .frame(width: 50, height: 50) 97 .aspectRatio(contentMode: .fit) 98 .foregroundStyle(WalletColors().attention) 99 .padding() 100 101 Text(title) 102 .talerFont(.title) 103 .padding(.bottom) 104 105 if let message { 106 if copyable { 107 if #available(iOS 16.4, *) { 108 Text(message).monospaced() 109 } else { 110 Text(message).font(.system(.body, design: .monospaced)) 111 } 112 113 CopyButton(textToCopy: message, isCopied: $isCopied, vertical: false) 114 .accessibilityLabel(Text("Copy the error JSON", comment: "a11y")) 115 .padding() 116 } else { 117 if let requestUrl { 118 Text(requestUrl) 119 .multilineTextAlignment(.center) 120 .padding() 121 } 122 Text(message) 123 .multilineTextAlignment(.center) 124 .padding() 125 } 126 } 127 } 128 } 129 .padding() 130 .safeAreaInset(edge: .bottom) { 131 Button("OK", role: .cancel) { 132 onDismiss() 133 } 134 .buttonStyle(TalerButtonStyle(type: .bordered)) 135 .padding(.bottom) 136 .padding(.horizontal) 137 } 138 } 139 }