WithdrawTOSView.swift (8029B)
1 /* 2 * This file is part of GNU Taler, ©2022-26 Taler Systems S.A. 3 * See LICENSE.md 4 */ 5 /** 6 * @author Marc Stibane 7 */ 8 import SwiftUI 9 import SymLog 10 import MarkdownUI 11 12 struct WithdrawTOSView: View { 13 private let symLog = SymLogV(0) 14 let stack: CallStack 15 let exchangeBaseUrl: String? 16 let viewID: Int // either VIEW_WITHDRAW_TOS or SHEET_WITHDRAW_TOS 17 let acceptAction: (() async -> Void)? 18 19 @Environment(\.dismiss) var dismiss // pop back once 20 @EnvironmentObject private var model: WalletModel 21 @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic 22 23 @State var exchangeTOS: ExchangeTermsOfService? 24 25 let navTitle = String(localized: "Terms of Service") 26 27 @MainActor 28 func loadToS(_ language: String) async { 29 if let exchangeBaseUrl { 30 let acceptedFormat: [String] = [MARKDOWN, PLAINTEXT] // MARKDOWN, HTML, PLAINTEXT 31 if let someTOS = try? await model.loadExchangeTermsOfService(exchangeBaseUrl, 32 acceptedFormat: acceptedFormat, 33 acceptLanguage: language) { 34 exchangeTOS = someTOS 35 } 36 } else { 37 // TODO: Yikes! No baseURL 38 } 39 } 40 41 @MainActor 42 func viewDidLoad() async { 43 if let exchangeTOS, let exchangeBaseUrl { 44 _ = try? await model.setExchangeTOSAccepted(exchangeBaseUrl) 45 if acceptAction != nil { 46 await acceptAction!() 47 } else { // just go back - caller will reload 48 dismiss() 49 } 50 } else { 51 // TODO: error 52 } 53 } 54 55 var body: some View { 56 let languageCode = Locale.preferredLanguageCode 57 // let languageName = Locale.current.localizedString(forLanguageCode: languageCode) 58 if let exchangeTOS, let exchangeBaseUrl { 59 Content(symLog: symLog, 60 tos: exchangeTOS, 61 myListStyle: $myListStyle, 62 language: languageCode, 63 languageAction: loadToS) { 64 Task { await viewDidLoad() } 65 } 66 .navigationTitle(navTitle) 67 .refreshable { 68 try? await model.updateExchange(exchangeBaseUrl: exchangeBaseUrl, force: true) 69 await loadToS(languageCode) 70 } 71 .onAppear() { 72 if viewID > SHEET_WITHDRAWAL { 73 DebugViewC.shared.setSheetID(SHEET_WITHDRAW_TOS) 74 } else { 75 DebugViewC.shared.setViewID(VIEW_WITHDRAW_TOS, stack: stack.push()) 76 } 77 } 78 } else { 79 let fallback = String(localized: "No payment service", comment: "loading") 80 LoadingView(stack: stack.push(), scopeInfo: nil, 81 message: exchangeBaseUrl?.trimURL ?? fallback) 82 .task { 83 await loadToS(languageCode) 84 } 85 } 86 } 87 } 88 // MARK: - 89 extension WithdrawTOSView { 90 91 static func cleanupText(_ term0: String) -> String { 92 let term1 = term0.replacingOccurrences(of: "\n ", with: " ") // newline + 5 blanks 93 let term2 = term1.replacingOccurrences(of: "\n ", with: " ") // newline + 4 blanks 94 let term3 = term2.replacingOccurrences(of: "\n ", with: " ") // newline + 3 blanks 95 let term4 = term3.replacingOccurrences(of: "\n ", with: " ") // newline + 2 blanks 96 let term5 = term4.replacingOccurrences(of: "\n ", with: " ") // newline + 1 blank 97 let term6 = term5.replacingOccurrences(of: "\n", with: " ") // remove all other linebreaks 98 let term7 = term6.replacingOccurrences(of: " ====", with: "\n====") // add them back for underscoring 99 let term8 = term7.replacingOccurrences(of: " ----", with: "\n----") // special for "Highlights:" 100 let term9 = term8.replacingOccurrences(of: " ****", with: "\n****") // special for "Terms of Service:" 101 return term9 102 } 103 104 struct plaintextToSV: View { 105 let plaintext: String 106 107 var body: some View { 108 let components = plaintext.components(separatedBy: "\n\n") 109 ForEach (components, id: \.self) { term0 in 110 Section { 111 Text(cleanupText(term0)) 112 .talerFont(.footnote) 113 .foregroundColor(Color(UIColor.label)) 114 } 115 } // for 116 } 117 } 118 119 struct Content: View { 120 let symLog: SymLogV 121 var tos: ExchangeTermsOfService 122 @Binding var myListStyle: MyListStyle 123 let language: String 124 var languageAction: (String) async -> () 125 var acceptAction: () -> () 126 127 @Environment(\.colorScheme) private var colorScheme 128 @Environment(\.colorSchemeContrast) private var colorSchemeContrast 129 #if DEBUG 130 @AppStorage("developerMode") var developerMode: Bool = true 131 #else 132 @AppStorage("developerMode") var developerMode: Bool = false 133 #endif 134 @State private var selectedLanguage = Locale.preferredLanguageCode 135 136 var showButton: Bool { 137 if tos.tosStatus == .missingTos { 138 // TODO: wallet-core should accept receiving money when there are no ToS 139 return developerMode // no devMode ==> false (don't show button) 140 } 141 if let acceptedEtag = tos.acceptedEtag { 142 if acceptedEtag == tos.currentEtag { 143 return false // user already accepted current ToS 144 } 145 } 146 return true 147 } 148 149 var body: some View { 150 let title = String(localized: "Language:", comment: "title of ToS language selection") 151 List { 152 if tos.tosAvailableLanguages.count > 1 { 153 Picker(title, selection: $selectedLanguage) { 154 ForEach(tos.tosAvailableLanguages, id: \.self) { code in 155 let languageName = Locale.current.localizedString(forLanguageCode: code) 156 Text(languageName ?? code) 157 } 158 } 159 .talerFont(.title3) 160 .pickerStyle(.menu) 161 .onAppear() { 162 withAnimation { selectedLanguage = language } 163 } 164 .onChange(of: selectedLanguage) { selected in 165 Task { 166 await languageAction(selected) 167 } 168 } 169 } 170 if tos.contentType == MARKDOWN { 171 Section { 172 let content = MarkdownContent(tos.content) 173 Markdown(content) 174 } 175 } else { 176 let components = tos.content.components(separatedBy: "\n\n") 177 ForEach (components, id: \.self) { term0 in 178 Section { 179 Text(cleanupText(term0)) 180 .talerFont(.footnote) 181 .foregroundColor(Color(UIColor.label)) 182 } 183 } // for 184 } // plain text 185 if showButton { 186 Section { 187 Button(String(localized: "Accept Terms of Service", comment: "Button"), action: acceptAction) 188 .buttonStyle(TalerButtonStyle(type: .prominent)) 189 .padding(.horizontal) 190 } header: { 191 Text("Accept", comment: "Heading for Accept ToS button") 192 .talerFont(.title3) 193 .foregroundColor(WalletColors().secondary(colorScheme, colorSchemeContrast)) 194 } 195 } 196 }.listStyle(myListStyle.style).anyView 197 } 198 } 199 }