taler-ios

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

ErrorView.swift (7491B)


      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     var onDismiss: (() -> Void)?
     76 
     77     init(_ stack: CallStack, title: String, message: String? = nil,
     78          requestUrl: String? = nil, copyable: Bool = false, onDismiss: (() -> Void)? = nil) {
     79         self.stack = stack
     80         self.title = title
     81         self.message = message
     82         self.requestUrl = requestUrl
     83         self.copyable = copyable
     84         self.onDismiss = onDismiss
     85     }
     86 
     87     init(_ stack: CallStack, error: Error, devMode: Bool, onDismiss: (() -> Void)? = nil) {
     88         let internetError = String(localized: "Network problem")
     89         let internetMessage = String(localized: "Please check your internet connection and try again.")
     90         let walletCoreError = String(localized: "Internal core error")
     91         let initializationError = String(localized: "Initialization error")
     92         let serializationError = String(localized: "Serialization error")
     93         let deserializationError = String(localized: "Deserialization error")
     94         let unknownError = String(localized: "Unknown error")
     95         let userCancelled = String(localized: "User cancelled")
     96 
     97         switch error {
     98             case let walletError as WalletBackendError:
     99                 switch walletError {
    100                     case .walletCoreError(let walletBackendResponseError):
    101                         if let responseError = walletBackendResponseError {
    102                             let code = responseError.code
    103                             if code == 7002 || code == 7005 || code == 7032 || code == 7036 {
    104                                 self.init(stack,
    105                                     title: code == 7036 ? userCancelled : internetError,
    106                                   message: responseError.hint ?? internetMessage,
    107                                requestUrl: responseError.requestUrl,
    108                                  copyable: false,
    109                                 onDismiss: onDismiss)
    110                                 break
    111                             }
    112                             // for now, always show JSON and allow to copy it
    113                             if let json = responseError.toJSON() {
    114                                 self.init(stack, title: walletCoreError, message: json, copyable: true, onDismiss: onDismiss)
    115                                 break
    116 //                            } else if let hint = responseError.errorResponse?.hint ?? responseError.hint {
    117 //                                self.init(stack, title: walletCoreError, message: hint, copyable: true, onDismiss: onDismiss)
    118 //                                break
    119 //                            } else if let message = responseError.message {
    120 //                                self.init(stack, title: walletCoreError, message: message, copyable: true, onDismiss: onDismiss)
    121 //                                break
    122                             }
    123                         }
    124                         self.init(stack, title: walletCoreError, copyable: false, onDismiss: onDismiss)
    125                     case .initializationError:
    126                         self.init(stack, title: initializationError, copyable: false, onDismiss: onDismiss)
    127                     case .serializationError:
    128                         self.init(stack, title: serializationError, copyable: false, onDismiss: onDismiss)
    129                     case .deserializationError:
    130                         self.init(stack, title: deserializationError, copyable: false, onDismiss: onDismiss)
    131                 }
    132             default:
    133                 self.init(stack, title: unknownError, message: error.localizedDescription, copyable: false, onDismiss: onDismiss)
    134         }
    135     }
    136 
    137     init(_ stack: CallStack, data: ErrorData, devMode: Bool, onDismiss: (() -> Void)? = nil) {
    138         switch data {
    139             case .message(let title, let message):
    140                 self.init(stack, title: title, message: message, copyable: false, onDismiss: onDismiss)
    141             case .error(let error):
    142                 self.init(stack, error: error, devMode: devMode, onDismiss: onDismiss)
    143         }
    144     }
    145 
    146     var body: some View {
    147         ScrollView {
    148             VStack {
    149                 Image(systemName: EXCLAMATION)
    150                     .resizable()
    151                     .frame(width: 50, height: 50)
    152                     .aspectRatio(contentMode: .fit)
    153                     .foregroundStyle(WalletColors().attention)
    154                     .padding()
    155 
    156                 Text(title)
    157                     .talerFont(.title)
    158                     .padding(.bottom)
    159 
    160                 if let message {
    161                     if copyable {
    162                         if #available(iOS 16.4, *) {
    163                             Text(message).monospaced()
    164                         } else {
    165                             Text(message).font(.system(.body, design: .monospaced))
    166                         }
    167 
    168                         CopyButton(message, vertical: false)
    169                             .accessibilityLabel(Text("Copy the error JSON", comment: "a11y"))
    170                             .padding()
    171                     } else {
    172                         if let requestUrl {
    173                             Text(requestUrl)
    174                                 .multilineTextAlignment(.center)
    175                                 .padding()
    176                         }
    177                         Text(message)
    178                             .multilineTextAlignment(.center)
    179                             .padding()
    180                     }
    181                 }
    182             }
    183         }
    184         .padding()
    185         .safeAreaInset(edge: .bottom) {
    186             Button("OK", role: .cancel) {
    187                 if let onDismiss {
    188                     onDismiss()
    189                 } else {
    190                     dismissTop(stack.push())
    191                 }
    192             }
    193             .buttonStyle(TalerButtonStyle(type: .bordered))
    194             .padding(.bottom)
    195             .padding(.horizontal)
    196         }
    197     }
    198 }