taler-ios

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

ErrorView.swift (8325B)


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