taler-ios

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

LoadingView.swift (6882B)


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