taler-ios

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

LoadingView.swift (6726B)


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