LoadingView.swift (5350B)
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 23 @State private var rotationEnabled = true 24 @State private var showAlert: Bool = false 25 @State private var disabled: Bool = false 26 @State private var isFinished: Bool? = false 27 28 private var dismissButton: some View { 29 Button("Cancel", role: .cancel) { 30 showAlert = false 31 if let lastError = controller.lastProgressError { 32 controller.lastProgressError = nil 33 Task { 34 try await model.cancelProgressToken(lastError.operation, 35 token: lastError.progressToken) 36 } } 37 } 38 .buttonStyle(TalerButtonStyle(type: .bordered, disabled: false)) 39 .padding(.horizontal) 40 } 41 42 @ViewBuilder 43 func retryButton(disabled: Bool) -> some View { 44 Button("Retry now") { 45 withAnimation { 46 showAlert = false 47 } 48 if let lastError = controller.lastProgressError { 49 controller.lastProgressError = nil 50 Task { 51 try await model.retryProgressTokenNow(lastError.operation, 52 token: lastError.progressToken) 53 } } 54 } 55 .buttonStyle(TalerButtonStyle(type: .prominent, disabled: disabled)) 56 .padding(.horizontal) 57 } 58 59 var body: some View { 60 let view = VStack(alignment: .center) { 61 if showAlert { 62 let lastError = controller.lastProgressError 63 let fallback = String(localized: "It seems that there is a problem with the network.") 64 Text("Couldn't establish a connection") 65 .talerFont(.title2) 66 .padding(.vertical) 67 Text(lastError?.error.hint ?? fallback) 68 .talerFont(.body) 69 Spacer() 70 if let lastPhase = controller.lastProgressPhase { 71 if lastPhase.phase == .stalled { 72 if let lastError, let delay = lastError.nextRetryDelay { 73 if isFinished == false { 74 TimeView("Next retry in:", seconds: Int(delay.toSeconds()), isFinished: $isFinished) 75 .talerFont(.title3) 76 .padding(.vertical) 77 } else if isFinished == true { 78 Text("Retrying…") 79 .talerFont(.title3) 80 } else { 81 Text("Waiting for response…") 82 .talerFont(.title3) 83 } 84 } 85 // Spacer() 86 retryButton(disabled: isFinished ?? false) 87 } 88 } 89 } else { 90 Spacer() 91 RotatingTaler(size: 100, 92 progress: true, // VoiceOver "In progress" 93 once: false, 94 rotationEnabled: $rotationEnabled) 95 .onTapGesture(count: 1) { 96 rotationEnabled.toggle() 97 } 98 Spacer() 99 if let scopeInfo { 100 if let urlStr = scopeInfo.url { 101 Text(urlStr.trimURL) 102 .talerFont(.title2) 103 } else { 104 Text(scopeInfo.currency) 105 .talerFont(.title2) 106 } 107 Spacer() 108 } 109 Text(message ?? EMPTYSTRING) 110 .talerFont(.title2) 111 Spacer() 112 } 113 dismissButton 114 } 115 .frame(maxWidth: .infinity) 116 .talerFont(.title) 117 .navigationTitle(navTitle) 118 .background(FullBackground()) 119 .onChange(of: controller.stalledConnection) { isStalled in 120 showAlert = isStalled 121 } 122 .onChange(of: controller.errorConnection) { isError in 123 if isError { 124 // print("❗️❗️❗️ error arrived, idle waiting starts") 125 isFinished = false 126 controller.errorConnection = false 127 } 128 } 129 130 if #available(iOS 17.0, *) { 131 view.toolbarTitleDisplayMode(.inlineLarge) 132 } else { 133 view 134 } 135 } 136 } 137 // MARK: - 138 struct LoadingView_Previews: PreviewProvider { 139 static var previews: some View { 140 NavigationView { 141 LoadingView(stack: CallStack("Loading"), scopeInfo: nil, message: "test message") // , backButtonHidden: true) 142 .navigationBarTitleDisplayMode(.automatic) 143 }.navigationViewStyle(.stack) 144 } 145 }