taler-ios

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

LoadingView.swift (7007B)


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