taler-ios

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

ErrorView.swift (7425B)


      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 BannerType {
     12     case error
     13     case networkUnavailable
     14     case warning5sec
     15 }
     16 
     17 struct ErrorBanner: View {
     18     let type: BannerType
     19     
     20     init(_ type: BannerType) {
     21         self.type = type
     22     }
     23 
     24     var errorText: String {
     25         switch type {
     26             case .error:
     27                 return "An error occurred…"
     28             case .networkUnavailable:
     29                 return "Network unavailable!"
     30             case .warning5sec:
     31                 return "Network is slow…"
     32         }
     33     }
     34 
     35     var errorColor: Color {
     36         switch type {
     37             case .error:
     38                 return .red
     39             case .networkUnavailable:
     40                 return .red
     41             case .warning5sec:
     42                 return .yellow
     43         }
     44     }
     45 
     46     var body: some View {
     47         Text(errorText)
     48             .foregroundStyle(.white)
     49             .frame(maxWidth: .infinity, alignment: .leading)
     50             .padding()
     51             .background {
     52                 RoundedRectangle(cornerRadius: 15)
     53                     .fill(errorColor)
     54             }
     55             .padding(.horizontal)
     56             .transition(.asymmetric(
     57                 insertion: .move(edge: .top),
     58                 removal: .move(edge: .top)
     59             ))
     60     }
     61 }
     62 
     63 enum ErrorData {
     64     case message(title: String, message: String)
     65     case error(Error)
     66 }
     67 
     68 struct ErrorView: View {
     69     let stack: CallStack
     70     var title: String
     71     var message: String? = nil
     72     var requestUrl: String? = nil
     73     var copyable: Bool
     74 
     75     @State var isCopied: Bool = false
     76 
     77     var onDismiss: () -> Void
     78 
     79     init(_ stack: CallStack, title: String, message: String? = nil,
     80       requestUrl: String? = nil, copyable: Bool, onDismiss: @escaping () -> Void) {
     81         self.stack = stack
     82         self.title = title
     83         self.message = message
     84         self.requestUrl = requestUrl
     85         self.copyable = copyable
     86         self.onDismiss = onDismiss
     87     }
     88 
     89     init(_ stack: CallStack, error: Error, devMode: Bool, onDismiss: @escaping () -> Void) {
     90         let internetError = String(localized: "Network problem")
     91         let internetMessage = String(localized: "Please check your internet connection and try again.")
     92         let walletCoreError = String(localized: "Internal core error")
     93         let initializationError = String(localized: "Initialization error")
     94         let serializationError = String(localized: "Serialization error")
     95         let deserializationError = String(localized: "Deserialization error")
     96         let unknownError = String(localized: "Unknown error")
     97         let userCancelled = String(localized: "User cancelled")
     98 
     99         switch error {
    100             case let walletError as WalletBackendError:
    101                 switch walletError {
    102                     case .walletCoreError(let walletBackendResponseError):
    103                         if let responseError = walletBackendResponseError {
    104                             let code = responseError.code
    105                             if code == 7002 || code == 7005 || code == 7032 || code == 7036 {
    106                                 self.init(stack,
    107                                     title: code == 7036 ? userCancelled : internetError,
    108                                   message: responseError.hint ?? internetMessage,
    109                                requestUrl: responseError.requestUrl,
    110                                  copyable: false,
    111                                 onDismiss: onDismiss)
    112                                 break
    113                             }
    114                             // for now, always show JSON and allow to copy it
    115                             if let json = responseError.toJSON() {
    116                                 self.init(stack, title: walletCoreError, message: json, copyable: true, onDismiss: onDismiss)
    117                                 break
    118 //                            } else if let hint = responseError.errorResponse?.hint ?? responseError.hint {
    119 //                                self.init(stack, title: walletCoreError, message: hint, copyable: true, onDismiss: onDismiss)
    120 //                                break
    121 //                            } else if let message = responseError.message {
    122 //                                self.init(stack, title: walletCoreError, message: message, copyable: true, onDismiss: onDismiss)
    123 //                                break
    124                             }
    125                         }
    126                         self.init(stack, title: walletCoreError, copyable: false, onDismiss: onDismiss)
    127                     case .initializationError:
    128                         self.init(stack, title: initializationError, copyable: false, onDismiss: onDismiss)
    129                     case .serializationError:
    130                         self.init(stack, title: serializationError, copyable: false, onDismiss: onDismiss)
    131                     case .deserializationError:
    132                         self.init(stack, title: deserializationError, copyable: false, onDismiss: onDismiss)
    133                 }
    134             default:
    135                 self.init(stack, title: unknownError, message: error.localizedDescription, copyable: false, onDismiss: onDismiss)
    136         }
    137     }
    138 
    139     init(_ stack: CallStack, data: ErrorData, devMode: Bool, onDismiss: @escaping () -> Void) {
    140         switch data {
    141             case .message(let title, let message):
    142                 self.init(stack, title: title, message: message, copyable: false, onDismiss: onDismiss)
    143             case .error(let error):
    144                 self.init(stack, error: error, devMode: devMode, onDismiss: onDismiss)
    145         }
    146     }
    147 
    148     var body: some View {
    149         ScrollView {
    150             VStack {
    151                 Image(systemName: EXCLAMATION)
    152                     .resizable()
    153                     .frame(width: 50, height: 50)
    154                     .aspectRatio(contentMode: .fit)
    155                     .foregroundStyle(WalletColors().attention)
    156                     .padding()
    157 
    158                 Text(title)
    159                     .talerFont(.title)
    160                     .padding(.bottom)
    161 
    162                 if let message {
    163                     if copyable {
    164                         if #available(iOS 16.4, *) {
    165                             Text(message).monospaced()
    166                         } else {
    167                             Text(message).font(.system(.body, design: .monospaced))
    168                         }
    169 
    170                         CopyButton(textToCopy: message, isCopied: $isCopied, vertical: false)
    171                             .accessibilityLabel(Text("Copy the error JSON", comment: "a11y"))
    172                             .padding()
    173                     } else {
    174                         if let requestUrl {
    175                             Text(requestUrl)
    176                                 .multilineTextAlignment(.center)
    177                                 .padding()
    178                         }
    179                         Text(message)
    180                             .multilineTextAlignment(.center)
    181                             .padding()
    182                     }
    183                 }
    184             }
    185         }
    186         .padding()
    187         .safeAreaInset(edge: .bottom) {
    188             Button("OK", role: .cancel) {
    189                 onDismiss()
    190             }
    191             .buttonStyle(TalerButtonStyle(type: .bordered))
    192             .padding(.bottom)
    193             .padding(.horizontal)
    194         }
    195     }
    196 }